第一次提交

This commit is contained in:
不明不惑
2026-03-03 01:23:02 +08:00
commit 3e434877e8
1053 changed files with 102411 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Abilities/GGA_AbilityCost.h"
bool UGGA_AbilityCost::CheckCost(const UGameplayAbility* Ability, const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
FGameplayTagContainer* OptionalRelevantTags) const
{
return BlueprintCheckCost(Ability, Handle, *ActorInfo, *OptionalRelevantTags);
}
void UGGA_AbilityCost::ApplyCost(const UGameplayAbility* Ability, const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo)
{
return BlueprintApplyCost(Ability, Handle, *ActorInfo, ActivationInfo);
}

View File

@@ -0,0 +1,350 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Abilities/GGA_AbilitySet.h"
#include "Abilities/GameplayAbility.h"
#include "Runtime/Launch/Resources/Version.h"
#include "AbilitySystemComponent.h"
#include "Utilities/GGA_AbilitySystemFunctionLibrary.h"
DEFINE_LOG_CATEGORY(LogGGA_AbilitySet)
#include UE_INLINE_GENERATED_CPP_BY_NAME(GGA_AbilitySet)
void FGGA_AbilitySet_GrantedHandles::AddAbilitySpecHandle(const FGameplayAbilitySpecHandle& Handle)
{
if (Handle.IsValid())
{
AbilitySpecHandles.Add(Handle);
}
}
void FGGA_AbilitySet_GrantedHandles::AddGameplayEffectHandle(const FActiveGameplayEffectHandle& Handle)
{
if (Handle.IsValid())
{
GameplayEffectHandles.Add(Handle);
}
}
void FGGA_AbilitySet_GrantedHandles::AddAttributeSet(UAttributeSet* Set)
{
GrantedAttributeSets.Add(Set);
}
void FGGA_AbilitySet_GrantedHandles::TakeFromAbilitySystem(UAbilitySystemComponent* ASC)
{
check(ASC);
if (!ASC->IsOwnerActorAuthoritative())
{
// Must be authoritative to give or take ability sets.
return;
}
for (const FGameplayAbilitySpecHandle& Handle : AbilitySpecHandles)
{
if (Handle.IsValid())
{
ASC->ClearAbility(Handle);
}
}
for (const FActiveGameplayEffectHandle& Handle : GameplayEffectHandles)
{
if (Handle.IsValid())
{
ASC->RemoveActiveGameplayEffect(Handle);
}
}
for (UAttributeSet* Set : GrantedAttributeSets)
{
ASC->RemoveSpawnedAttribute(Set);
}
AbilitySpecHandles.Reset();
GameplayEffectHandles.Reset();
GrantedAttributeSets.Reset();
}
UGGA_AbilitySet::UGGA_AbilitySet(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UGGA_AbilitySet::GiveToAbilitySystem(UAbilitySystemComponent* ASC, FGGA_AbilitySet_GrantedHandles* OutGrantedHandles, UObject* SourceObject, int32 OverrideLevel) const
{
check(ASC);
if (!ASC->IsOwnerActorAuthoritative())
{
// Must be authoritative to give or take ability sets.
return;
}
// Grant the attribute sets.
for (int32 SetIndex = 0; SetIndex < GrantedAttributes.Num(); ++SetIndex)
{
const FGGA_AbilitySet_AttributeSet& SetToGrant = GrantedAttributes[SetIndex];
const TSubclassOf<UAttributeSet> AttributeSetClass = SetToGrant.AttributeSet.LoadSynchronous();
if (!AttributeSetClass)
{
UE_LOG(LogGGA_AbilitySet, Error, TEXT("GrantedAttributes[%d] on ability set [%s]: AttributeSet is not valid"), SetIndex, *GetNameSafe(this));
continue;
}
if (UAttributeSet* ExistingOne = UGGA_AbilitySystemFunctionLibrary::GetAttributeSetByClass(ASC, AttributeSetClass))
{
UE_LOG(LogGGA_AbilitySet, Error, TEXT("GrantedAttributes[%d] on ability set [%s]: AttributeSet already exists."), SetIndex, *GetNameSafe(this));
continue;
}
#if WITH_EDITORONLY_DATA
if (!SetToGrant.bAttributeSetEnabled)
{
UE_LOG(LogGGA_AbilitySet, Display, TEXT( "GrantedAttributes[%d] on ability set [%s]:skipped for debugging."), SetIndex, *GetNameSafe(this));
continue;
}
#endif
UAttributeSet* NewSet = NewObject<UAttributeSet>(ASC->GetOwner(), AttributeSetClass);
ASC->AddAttributeSetSubobject(NewSet);
if (OutGrantedHandles)
{
OutGrantedHandles->AddAttributeSet(NewSet);
}
}
// Grant the gameplay abilities.
for (int32 AbilityIndex = 0; AbilityIndex < GrantedGameplayAbilities.Num(); ++AbilityIndex)
{
const FGGA_AbilitySet_GameplayAbility& AbilityToGrant = GrantedGameplayAbilities[AbilityIndex];
const TSubclassOf<UGameplayAbility> AbilityClass = AbilityToGrant.Ability.LoadSynchronous();
if (!AbilityClass)
{
UE_LOG(LogGGA_AbilitySet, Error, TEXT("GrantedGameplayAbilities[%d] on ability set [%s]: Ability class is not valid."), AbilityIndex, *GetNameSafe(this));
continue;
}
#if WITH_EDITORONLY_DATA
if (!AbilityToGrant.bAbilityEnabled)
{
UE_LOG(LogGGA_AbilitySet, Display, TEXT( "GrantedGameplayAbilities[%d] on ability set [%s]: Skipped for debugging."), AbilityIndex, *GetNameSafe(this));
continue;
}
#endif
UGameplayAbility* AbilityCDO = AbilityClass->GetDefaultObject<UGameplayAbility>();
FGameplayAbilitySpec AbilitySpec(AbilityCDO, OverrideLevel > 0 ? OverrideLevel : AbilityToGrant.AbilityLevel);
AbilitySpec.SourceObject = SourceObject;
if (AbilityToGrant.InputID > 0)
{
AbilitySpec.InputID = AbilityToGrant.InputID;
}
if (!AbilityToGrant.DynamicTags.IsEmpty())
{
#if ENGINE_MINOR_VERSION > 4
AbilitySpec.GetDynamicSpecSourceTags().AppendTags(AbilityToGrant.DynamicTags);
#else
AbilitySpec.DynamicAbilityTags.AppendTags(AbilityToGrant.DynamicTags);
#endif
}
const FGameplayAbilitySpecHandle AbilitySpecHandle = ASC->GiveAbility(AbilitySpec);
if (OutGrantedHandles)
{
OutGrantedHandles->AddAbilitySpecHandle(AbilitySpecHandle);
}
}
// Grant the gameplay effects.
for (int32 EffectIndex = 0; EffectIndex < GrantedGameplayEffects.Num(); ++EffectIndex)
{
const FGGA_AbilitySet_GameplayEffect& EffectToGrant = GrantedGameplayEffects[EffectIndex];
const TSubclassOf<UGameplayEffect> EffectClass = EffectToGrant.GameplayEffect.LoadSynchronous();
if (!EffectClass)
{
UE_LOG(LogGGA_AbilitySet, Error, TEXT("GrantedGameplayEffects[%d] on ability set [%s]:Effect Class is not valid"), EffectIndex, *GetNameSafe(this));
continue;
}
#if WITH_EDITORONLY_DATA
if (!EffectToGrant.bEffectEnabled)
{
UE_LOG(LogGGA_AbilitySet, Display, TEXT( "GrantedGameplayEffects[%d] on ability set [%s]:Skipped for debugging."), EffectIndex, *GetNameSafe(this));
continue;
}
#endif
const UGameplayEffect* GameplayEffectCDO = EffectClass->GetDefaultObject<UGameplayEffect>();
const FActiveGameplayEffectHandle GameplayEffectHandle = ASC->
ApplyGameplayEffectToSelf(GameplayEffectCDO, OverrideLevel > 0 ? OverrideLevel : EffectToGrant.EffectLevel, ASC->MakeEffectContext());
if (OutGrantedHandles)
{
OutGrantedHandles->AddGameplayEffectHandle(GameplayEffectHandle);
if (GameplayEffectCDO->DurationPolicy == EGameplayEffectDurationType::Infinite && !GameplayEffectHandle.IsValid())
{
UE_LOG(LogGGA_AbilitySet, Warning, TEXT("Granted Infinite GameplayEffects[%d] on ability set [%s] failed to apply"), EffectIndex, *GetNameSafe(this));
}
}
}
}
FGGA_AbilitySet_GrantedHandles UGGA_AbilitySet::GiveAbilitySetToAbilitySystem(TSoftObjectPtr<UGGA_AbilitySet> AbilitySet, UAbilitySystemComponent* ASC, UObject* SourceObject, int32 OverrideLevel)
{
FGGA_AbilitySet_GrantedHandles GrantedHandles;
if (IsValid(ASC) && !AbilitySet.IsNull())
{
if (!AbilitySet.IsValid())
{
AbilitySet.LoadSynchronous();
}
AbilitySet->GiveToAbilitySystem(ASC, &GrantedHandles, SourceObject, OverrideLevel);
}
return GrantedHandles;
}
TArray<FGGA_AbilitySet_GrantedHandles> UGGA_AbilitySet::GiveAbilitySetsToAbilitySystem(TArray<TSoftObjectPtr<UGGA_AbilitySet>> AbilitySets, UAbilitySystemComponent* ASC, UObject* SourceObject,
int32 OverrideLevel)
{
TArray<FGGA_AbilitySet_GrantedHandles> Handles;
for (auto& AbilitySet : AbilitySets)
{
Handles.Add(GiveAbilitySetToAbilitySystem(AbilitySet, ASC, SourceObject, OverrideLevel));
}
return Handles;
}
void UGGA_AbilitySet::TakeAbilitySetFromAbilitySystem(FGGA_AbilitySet_GrantedHandles& GrantedHandles, UAbilitySystemComponent* ASC)
{
if (IsValid(ASC))
{
GrantedHandles.TakeFromAbilitySystem(ASC);
}
}
void UGGA_AbilitySet::TakeAbilitySetsFromAbilitySystem(TArray<FGGA_AbilitySet_GrantedHandles>& GrantedHandles, UAbilitySystemComponent* ASC)
{
if (IsValid(ASC))
{
for (FGGA_AbilitySet_GrantedHandles& Handle : GrantedHandles)
{
Handle.TakeFromAbilitySystem(ASC);
}
}
}
#if WITH_EDITOR
#include "UObject/ObjectSaveContext.h"
void FGGA_AbilitySet_GameplayAbility::MakeEditorFriendlyName()
{
EditorFriendlyName = "Empty Ability";
if (!Ability.IsNull())
{
if (TSubclassOf<UGameplayAbility> Loaded = Ability.LoadSynchronous())
{
EditorFriendlyName = Loaded->GetDisplayNameText().ToString();
}
}
}
void FGGA_AbilitySet_GameplayEffect::MakeEditorFriendlyName()
{
EditorFriendlyName = "Empty Effect";
if (!GameplayEffect.IsNull())
{
if (TSubclassOf<UGameplayEffect> Loaded = GameplayEffect.LoadSynchronous())
{
EditorFriendlyName = Loaded->GetDisplayNameText().ToString();
}
}
}
void FGGA_AbilitySet_AttributeSet::MakeEditorFriendlyName()
{
EditorFriendlyName = "Empty Attribute Set";
if (!AttributeSet.IsNull())
{
if (TSubclassOf<UAttributeSet> Loaded = AttributeSet.LoadSynchronous())
{
EditorFriendlyName = Loaded->GetDisplayNameText().ToString();
}
}
}
void UGGA_AbilitySet::PreSave(FObjectPreSaveContext SaveContext)
{
Super::PreSave(SaveContext);
if (!IsRunningCommandlet())
{
for (auto& Ability : GrantedGameplayAbilities)
{
Ability.MakeEditorFriendlyName();
}
for (auto& Effect : GrantedGameplayEffects)
{
Effect.MakeEditorFriendlyName();
}
for (auto& Attribute : GrantedAttributes)
{
Attribute.MakeEditorFriendlyName();
}
}
}
void UGGA_AbilitySet::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
}
void UGGA_AbilitySet::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
{
FName MemberName = PropertyChangedEvent.PropertyChain.GetActiveMemberNode()->GetValue()->GetFName();
if (PropertyChangedEvent.GetPropertyName() == TEXT("Ability") && MemberName == GET_MEMBER_NAME_CHECKED(UGGA_AbilitySet, GrantedGameplayAbilities))
{
const int32 Index = PropertyChangedEvent.GetArrayIndex(GET_MEMBER_NAME_CHECKED(UGGA_AbilitySet, GrantedGameplayAbilities).ToString());
if (Index != INDEX_NONE)
{
GrantedGameplayAbilities[Index].MakeEditorFriendlyName();
}
}
if (PropertyChangedEvent.GetPropertyName() == TEXT("GameplayEffect") && MemberName == GET_MEMBER_NAME_CHECKED(UGGA_AbilitySet, GrantedGameplayEffects))
{
const int32 Index = PropertyChangedEvent.GetArrayIndex(GET_MEMBER_NAME_CHECKED(UGGA_AbilitySet, GrantedGameplayEffects).ToString());
if (Index != INDEX_NONE)
{
GrantedGameplayEffects[Index].MakeEditorFriendlyName();
}
}
if (PropertyChangedEvent.GetPropertyName() == TEXT("AttributeSet") && MemberName == GET_MEMBER_NAME_CHECKED(UGGA_AbilitySet, GrantedAttributes))
{
const int32 Index = PropertyChangedEvent.GetArrayIndex(GET_MEMBER_NAME_CHECKED(UGGA_AbilitySet, GrantedGameplayEffects).ToString());
if (Index != INDEX_NONE)
{
GrantedAttributes[Index].MakeEditorFriendlyName();
}
}
Super::PostEditChangeChainProperty(PropertyChangedEvent);
}
#endif

View File

@@ -0,0 +1,651 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Abilities/GGA_GameplayAbility.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemGlobals.h"
#include "AbilitySystemLog.h"
#include "Runtime/Launch/Resources/Version.h"
#include "Abilities/GGA_AbilityCost.h"
#include "GGA_AbilitySystemComponent.h"
#include "GGA_GameplayTags.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/Controller.h"
#include "GameFramework/Pawn.h"
#include "GGA_LogChannels.h"
#include "Misc/DataValidation.h"
#include "Utilities/GGA_GameplayEffectContainerFunctionLibrary.h"
#define ENSURE_ABILITY_IS_INSTANTIATED_OR_RETURN(FunctionName, ReturnValue) \
{ \
if (!ensure(IsInstantiated())) \
{ \
ABILITY_LOG(Error, TEXT("%s: " #FunctionName " cannot be called on a non-instanced ability. Check the instancing policy."), *GetPathName()); \
return ReturnValue; \
} \
}
UGGA_GameplayAbility::UGGA_GameplayAbility(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateNo;
bServerRespectsRemoteAbilityCancellation = false;
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
NetSecurityPolicy = EGameplayAbilityNetSecurityPolicy::ClientOrServer;
ActivationGroup = EGGA_AbilityActivationGroup::Independent;
bReplicateInputDirectly = false;
bEnableTick = false;
}
void UGGA_GameplayAbility::Tick(float DeltaTime)
{
AbilityTick(DeltaTime);
}
TStatId UGGA_GameplayAbility::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(UGGA_GameplayAbility, STATGROUP_GameplayAbility);
}
bool UGGA_GameplayAbility::IsTickable() const
{
return IsInstantiated() && bEnableTick && GetInstancingPolicy() == EGameplayAbilityInstancingPolicy::InstancedPerActor && IsActive();
}
void UGGA_GameplayAbility::AbilityTick_Implementation(float DeltaTime)
{
}
AController* UGGA_GameplayAbility::GetControllerFromActorInfo() const
{
if (CurrentActorInfo)
{
if (AController* PC = CurrentActorInfo->PlayerController.Get())
{
return PC;
}
// Look for a player controller or pawn in the owner chain.
AActor* TestActor = CurrentActorInfo->OwnerActor.Get();
while (TestActor)
{
if (AController* C = Cast<AController>(TestActor))
{
return C;
}
if (APawn* Pawn = Cast<APawn>(TestActor))
{
return Pawn->GetController();
}
TestActor = TestActor->GetOwner();
}
}
return nullptr;
}
void UGGA_GameplayAbility::SetActivationGroup(EGGA_AbilityActivationGroup NewGroup)
{
ActivationGroup = NewGroup;
}
void UGGA_GameplayAbility::TryActivateAbilityOnSpawn(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) const
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
#if ENGINE_MINOR_VERSION > 4
// Fixing this up to use the instance activation, but this function should be deprecated as it cannot work with InstancedPerExecution
UE_CLOG(Spec.Ability->GetInstancingPolicy() == EGameplayAbilityInstancingPolicy::InstancedPerExecution, LogAbilitySystem, Warning,
TEXT("%hs: %s is InstancedPerExecution. This is unreliable for Input as you may only interact with the latest spawned Instance"), __func__, *GetNameSafe(Spec.Ability));
TArray<UGameplayAbility*> Instances = Spec.GetAbilityInstances();
const FGameplayAbilityActivationInfo& ActivationInfo = Instances.IsEmpty() ? Spec.ActivationInfo : Instances.Last()->GetCurrentActivationInfoRef();
const bool bIsPredicting = (ActivationInfo.ActivationMode == EGameplayAbilityActivationMode::Predicting);
#else
const bool bIsPredicting = (Spec.ActivationInfo.ActivationMode == EGameplayAbilityActivationMode::Predicting);
#endif
PRAGMA_ENABLE_DEPRECATION_WARNINGS
// Try to activate if activation policy is on spawn.
#if ENGINE_MINOR_VERSION > 4
if (ActorInfo && !Spec.IsActive() && !bIsPredicting && GetAssetTags().HasTagExact(GGA_AbilityTraitTags::ActivationOnSpawn))
#else
if (ActorInfo && !Spec.IsActive() && !bIsPredicting && AbilityTags.HasTagExact(GGA_AbilityTraitTags::ActivationOnSpawn))
#endif
{
UAbilitySystemComponent* ASC = ActorInfo->AbilitySystemComponent.Get();
const AActor* AvatarActor = ActorInfo->AvatarActor.Get();
// If avatar actor is torn off or about to die, don't try to activate until we get the new one.
if (ASC && AvatarActor && !AvatarActor->GetTearOff() && (AvatarActor->GetLifeSpan() <= 0.0f))
{
const bool bIsLocalExecution = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalPredicted) || (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalOnly);
const bool bIsServerExecution = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerOnly) || (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerInitiated);
const bool bClientShouldActivate = ActorInfo->IsLocallyControlled() && bIsLocalExecution;
const bool bServerShouldActivate = ActorInfo->IsNetAuthority() && bIsServerExecution;
if (bClientShouldActivate || bServerShouldActivate)
{
ASC->TryActivateAbility(Spec.Handle);
}
}
}
}
void UGGA_GameplayAbility::HandleActivationFailed(const FGameplayTagContainer& FailedReason) const
{
OnActivationFailed(FailedReason);
}
bool UGGA_GameplayAbility::HasEffectContainer(FGameplayTag ContainerTag)
{
return EffectContainerMap.Contains(ContainerTag);
}
FGGA_GameplayEffectContainerSpec UGGA_GameplayAbility::MakeEffectContainerSpec(FGameplayTag ContainerTag, const FGameplayEventData& EventData, int32 OverrideGameplayLevel)
{
FGGA_GameplayEffectContainer* FoundContainer = EffectContainerMap.Find(ContainerTag);
if (FoundContainer)
{
return UGGA_GameplayEffectContainerFunctionLibrary::MakeEffectContainerSpec(*FoundContainer, EventData, OverrideGameplayLevel, this);
}
return FGGA_GameplayEffectContainerSpec();
}
TArray<FActiveGameplayEffectHandle> UGGA_GameplayAbility::ApplyEffectContainer(FGameplayTag ContainerTag, const FGameplayEventData& EventData, int32 OverrideGameplayLevel)
{
FGGA_GameplayEffectContainer* FoundContainer = EffectContainerMap.Find(ContainerTag);
if (FoundContainer)
{
const FGGA_GameplayEffectContainerSpec Spec = UGGA_GameplayEffectContainerFunctionLibrary::MakeEffectContainerSpec(*FoundContainer, EventData, OverrideGameplayLevel, this);
return UGGA_GameplayEffectContainerFunctionLibrary::ApplyEffectContainerSpec(this, Spec);
}
return TArray<FActiveGameplayEffectHandle>();
}
void UGGA_GameplayAbility::PreActivate(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData)
{
Super::PreActivate(Handle, ActorInfo, ActivationInfo, OnGameplayAbilityEndedDelegate, TriggerEventData);
UAbilitySystemComponent* Comp = ActorInfo->AbilitySystemComponent.Get();
for (const FGGA_GameplayTagCount& TagCount : ActivationOwnedLooseTags)
{
Comp->AddLooseGameplayTag(TagCount.Tag, TagCount.Count);
}
}
void UGGA_GameplayAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
bool bReplicateEndAbility, bool bWasCancelled)
{
if (IsEndAbilityValid(Handle, ActorInfo))
{
if (UAbilitySystemComponent* Comp = ActorInfo->AbilitySystemComponent.Get())
{
for (const FGGA_GameplayTagCount& TagCount : ActivationOwnedLooseTags)
{
Comp->RemoveLooseGameplayTag(TagCount.Tag, TagCount.Count);
}
}
}
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
void UGGA_GameplayAbility::OnActivationFailed_Implementation(const FGameplayTagContainer& FailedReason) const
{
}
bool UGGA_GameplayAbility::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags,
const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const
{
if (!ActorInfo || !ActorInfo->AbilitySystemComponent.IsValid())
{
return false;
}
if (!Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags))
{
return false;
}
//@TODO Possibly remove after setting up tag relationships
UGGA_AbilitySystemComponent* GASC = CastChecked<UGGA_AbilitySystemComponent>(ActorInfo->AbilitySystemComponent.Get());
if (GASC->IsActivationGroupBlocked(ActivationGroup))
{
if (OptionalRelevantTags)
{
OptionalRelevantTags->AddTag(GGA_AbilityActivateFailTags::ActivationGroup);
}
return false;
}
return true;
}
void UGGA_GameplayAbility::SetCanBeCanceled(bool bCanBeCanceled)
{
// The ability can not block canceling if it's replaceable.
if (!bCanBeCanceled && (ActivationGroup == EGGA_AbilityActivationGroup::Exclusive_Replaceable))
{
UE_LOG(LogGGA_Ability, Error, TEXT("SetCanBeCanceled: Ability [%s] can not block canceling because its activation group is replaceable."), *GetName());
return;
}
Super::SetCanBeCanceled(bCanBeCanceled);
}
void UGGA_GameplayAbility::OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
{
Super::OnGiveAbility(ActorInfo, Spec);
K2_OnGiveAbility();
TryActivateAbilityOnSpawn(ActorInfo, Spec);
}
void UGGA_GameplayAbility::OnRemoveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
{
K2_OnRemoveAbility();
Super::OnRemoveAbility(ActorInfo, Spec);
}
void UGGA_GameplayAbility::OnAvatarSet(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
{
Super::OnAvatarSet(ActorInfo, Spec);
K2_OnAvatarSet();
}
bool UGGA_GameplayAbility::ShouldActivateAbility(ENetRole Role) const
{
return K2_ShouldActivateAbility(Role) && Super::ShouldActivateAbility(Role);
// Don't violate security policy if we're not the server
}
bool UGGA_GameplayAbility::K2_ShouldActivateAbility_Implementation(ENetRole Role) const
{
return true;
}
void UGGA_GameplayAbility::InputPressed(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo)
{
Super::InputPressed(Handle, ActorInfo, ActivationInfo);
K2_OnInputPressed(Handle, *ActorInfo, ActivationInfo);
}
void UGGA_GameplayAbility::InputReleased(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo)
{
Super::InputReleased(Handle, ActorInfo, ActivationInfo);
K2_OnInputReleased(Handle, *ActorInfo, ActivationInfo);
}
bool UGGA_GameplayAbility::IsInputPressed() const
{
FGameplayAbilitySpec* Spec = GetCurrentAbilitySpec();
return Spec && Spec->InputPressed;
}
bool UGGA_GameplayAbility::BatchRPCTryActivateAbility(FGameplayAbilitySpecHandle InAbilityHandle, bool EndAbilityImmediately)
{
UGGA_AbilitySystemComponent* ASC = Cast<UGGA_AbilitySystemComponent>(GetAbilitySystemComponentFromActorInfo());
if (ASC)
{
return ASC->BatchRPCTryActivateAbility(InAbilityHandle, EndAbilityImmediately);
}
return false;
}
void UGGA_GameplayAbility::ExternalEndAbility()
{
check(CurrentActorInfo);
bool bReplicateEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicateEndAbility, bWasCancelled);
}
bool UGGA_GameplayAbility::CheckCost(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
OUT FGameplayTagContainer* OptionalRelevantTags) const
{
if (!Super::CheckCost(Handle, ActorInfo, OptionalRelevantTags))
{
return false;
}
if (!K2_OnCheckCost(Handle, *ActorInfo))
{
return false;
}
for (TObjectPtr<UGGA_AbilityCost> AdditionalCost : AdditionalCosts)
{
if (AdditionalCost != nullptr)
{
if (!AdditionalCost->CheckCost(this, Handle, ActorInfo, OptionalRelevantTags))
{
return false;
}
}
}
return true;
}
bool UGGA_GameplayAbility::K2_OnCheckCost_Implementation(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo& ActorInfo) const
{
return true;
}
void UGGA_GameplayAbility::ApplyCost(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo) const
{
Super::ApplyCost(Handle, ActorInfo, ActivationInfo);
check(ActorInfo);
K2_OnApplyCost(Handle, *ActorInfo, ActivationInfo);
// Used to determine if the ability actually hit a target (as some costs are only spent on successful attempts)
auto DetermineIfAbilityHitTarget = [&]()
{
if (ActorInfo->IsNetAuthority())
{
if (UGGA_AbilitySystemComponent* ASC = Cast<UGGA_AbilitySystemComponent>(ActorInfo->AbilitySystemComponent.Get()))
{
FGameplayAbilityTargetDataHandle TargetData;
ASC->GetAbilityTargetData(Handle, ActivationInfo, TargetData);
for (int32 TargetDataIdx = 0; TargetDataIdx < TargetData.Data.Num(); ++TargetDataIdx)
{
if (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetData, TargetDataIdx))
{
return true;
}
}
}
}
return false;
};
// Pay any additional costs
bool bAbilityHitTarget = false;
bool bHasDeterminedIfAbilityHitTarget = false;
for (TObjectPtr<UGGA_AbilityCost> AdditionalCost : AdditionalCosts)
{
if (AdditionalCost != nullptr)
{
if (AdditionalCost->ShouldOnlyApplyCostOnHit())
{
if (!bHasDeterminedIfAbilityHitTarget)
{
bAbilityHitTarget = DetermineIfAbilityHitTarget();
bHasDeterminedIfAbilityHitTarget = true;
}
if (!bAbilityHitTarget)
{
continue;
}
}
AdditionalCost->ApplyCost(this, Handle, ActorInfo, ActivationInfo);
}
}
}
UGameplayEffect* UGGA_GameplayAbility::GetCostGameplayEffect() const
{
if (TSubclassOf<UGameplayEffect> GE = K2_GetCostGameplayEffect())
{
if (GE)
{
return GE->GetDefaultObject<UGameplayEffect>();
}
return nullptr;
}
return nullptr;
}
TSubclassOf<UGameplayEffect> UGGA_GameplayAbility::K2_GetCostGameplayEffect_Implementation() const
{
return CostGameplayEffectClass;
}
void UGGA_GameplayAbility::K2_OnApplyCost_Implementation(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo& ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo) const
{
}
void UGGA_GameplayAbility::ApplyAbilityTagsToGameplayEffectSpec(FGameplayEffectSpec& Spec, FGameplayAbilitySpec* AbilitySpec) const
{
Super::ApplyAbilityTagsToGameplayEffectSpec(Spec, AbilitySpec);
}
bool UGGA_GameplayAbility::DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& AbilitySystemComponent, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags,
FGameplayTagContainer* OptionalRelevantTags) const
{
// Define a common lambda to check for blocked tags
bool bBlocked = false;
auto CheckForBlocked = [&](const FGameplayTagContainer& ContainerA, const FGameplayTagContainer& ContainerB)
{
// Do we not have any tags in common? Then we're not blocked
if (ContainerA.IsEmpty() || ContainerB.IsEmpty() || !ContainerA.HasAny(ContainerB))
{
return;
}
if (OptionalRelevantTags)
{
// Ensure the global blocking tag is only added once
if (!bBlocked)
{
UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
const FGameplayTag& BlockedTag = AbilitySystemGlobals.ActivateFailTagsBlockedTag;
OptionalRelevantTags->AddTag(BlockedTag);
}
// Now append all the blocking tags
OptionalRelevantTags->AppendMatchingTags(ContainerA, ContainerB);
}
bBlocked = true;
};
// Define a common lambda to check for missing required tags
bool bMissing = false;
auto CheckForRequired = [&](const FGameplayTagContainer& TagsToCheck, const FGameplayTagContainer& RequiredTags)
{
// Do we have no requirements, or have met all requirements? Then nothing's missing
if (RequiredTags.IsEmpty() || TagsToCheck.HasAll(RequiredTags))
{
return;
}
if (OptionalRelevantTags)
{
// Ensure the global missing tag is only added once
if (!bMissing)
{
UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
const FGameplayTag& MissingTag = AbilitySystemGlobals.ActivateFailTagsMissingTag;
OptionalRelevantTags->AddTag(MissingTag);
}
FGameplayTagContainer MissingTags = RequiredTags;
MissingTags.RemoveTags(TagsToCheck.GetGameplayTagParents());
OptionalRelevantTags->AppendTags(MissingTags);
}
bMissing = true;
};
const UGGA_AbilitySystemComponent* GASC = Cast<UGGA_AbilitySystemComponent>(&AbilitySystemComponent);
static FGameplayTagContainer AllRequiredTags;
static FGameplayTagContainer AllBlockedTags;
AllRequiredTags = ActivationRequiredTags;
AllBlockedTags = ActivationBlockedTags;
// Expand our ability tags to add additional required/blocked tags
if (GASC)
{
GASC->GetAdditionalActivationTagRequirements(GetAssetTags(), AllRequiredTags, AllBlockedTags);
}
// Start by checking all of the blocked tags first (so OptionalRelevantTags will contain blocked tags first)
CheckForBlocked(GetAssetTags(), AbilitySystemComponent.GetBlockedAbilityTags());
CheckForBlocked(AbilitySystemComponent.GetOwnedGameplayTags(), AllBlockedTags);
// Check to see the required/blocked tags for this ability
if (AllBlockedTags.Num() || AllRequiredTags.Num())
{
static FGameplayTagContainer AbilitySystemComponentTags;
AbilitySystemComponentTags.Reset();
AbilitySystemComponent.GetOwnedGameplayTags(AbilitySystemComponentTags);
if (AbilitySystemComponentTags.HasAny(AllBlockedTags))
{
if (OptionalRelevantTags)
{
OptionalRelevantTags->AppendTags(AllBlockedTags);
}
bBlocked = true;
}
if (!AbilitySystemComponentTags.HasAll(AllRequiredTags))
{
if (OptionalRelevantTags)
{
OptionalRelevantTags->AppendTags(AllRequiredTags);
}
bMissing = true;
}
}
if (SourceTags != nullptr)
{
CheckForBlocked(*SourceTags, SourceBlockedTags);
}
if (TargetTags != nullptr)
{
CheckForBlocked(*TargetTags, TargetBlockedTags);
}
// Now check all required tags
CheckForRequired(AbilitySystemComponent.GetOwnedGameplayTags(), AllRequiredTags);
if (SourceTags != nullptr)
{
CheckForRequired(*SourceTags, SourceRequiredTags);
}
if (TargetTags != nullptr)
{
CheckForRequired(*TargetTags, TargetRequiredTags);
}
if (!bBlocked && !bMissing)
{
// If it's a custom implementation that blocks, we can't specify exactly which tag so just use the generic
bBlocked = AbilitySystemComponent.AreAbilityTagsBlocked(GetAssetTags());
if (bBlocked && OptionalRelevantTags)
{
UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
const FGameplayTag& BlockedTag = AbilitySystemGlobals.ActivateFailTagsBlockedTag;
OptionalRelevantTags->AddTag(BlockedTag);
}
}
// We succeeded if there were no blocked tags and no missing required tags
return !bBlocked && !bMissing;
}
void UGGA_GameplayAbility::SendTargetDataToServer(const FGameplayAbilityTargetDataHandle& TargetData)
{
if (IsPredictingClient())
{
UAbilitySystemComponent* ASC = CurrentActorInfo->AbilitySystemComponent.Get();
check(ASC);
// Create new prediction window for next operation. 为接下来的操作新增一个pk
FScopedPredictionWindow(ASC, true);
FGameplayTag ApplicationTag;
// tell server about it. 告诉服务器设置TargetData,传入技能的uid和激活id,Data和本次操作的id
CurrentActorInfo->AbilitySystemComponent->CallServerSetReplicatedTargetData(
CurrentSpecHandle, CurrentActivationInfo.GetActivationPredictionKey(),
TargetData, ApplicationTag, ASC->ScopedPredictionKey);
}
}
#if WITH_EDITOR
EDataValidationResult UGGA_GameplayAbility::IsDataValid(FDataValidationContext& Context) const
{
if (bReplicateInputDirectly == true)
{
Context.AddError(FText::FromString(TEXT("bReplicateInputDirectly is not recommended to use according to best practices.")));
return EDataValidationResult::Invalid;
}
if (bServerRespectsRemoteAbilityCancellation == true)
{
Context.AddError(FText::FromString(TEXT("bServerRespectsRemoteAbilityCancellation is not recommended to use according to best practices.")));
return EDataValidationResult::Invalid;
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (InstancingPolicy == EGameplayAbilityInstancingPolicy::NonInstanced)
{
Context.AddError(FText::FromString(TEXT("NonInstanced ability is deprecated since UE5.5, Use InstancedPerActor as the default to avoid confusing corner cases")));
return EDataValidationResult::Invalid;
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
// if (ReplicationPolicy == EGameplayAbilityReplicationPolicy::Type::ReplicateYes)
// {
// Context.AddError(FText::FromString(TEXT("ReplicationPolicy->ReplicateYes is not acceptable, Pelease use other option!")));
// return EDataValidationResult::Invalid;
// }
// if (!AbilityTriggers.IsEmpty() && NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::Type::ServerInitiated)
// {
// ValidationErrors.Add(FText::FromString(TEXT("Ability with triggers doesn't work with ServerInitiated Net Execution Policy!")));
// return EDataValidationResult::Invalid;
// }
// if (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::Type::ServerInitiated)
// {
// ValidationErrors.Add(FText::FromString(TEXT("NetExecutionPolicy->ServerInitiated is not acceptable, Pelease use other option!")));
// return EDataValidationResult::Invalid;
// }
if (bHasBlueprintActivateFromEvent && bHasBlueprintActivate && !AbilityTriggers.IsEmpty())
{
Context.AddError(FText::FromString(TEXT("ActivateAbilityFromEvent will not run! Please remove ActivateAbility node!")));
return EDataValidationResult::Invalid;
}
return Super::IsDataValid(Context);
}
#include "UObject/ObjectSaveContext.h"
void UGGA_GameplayAbility::PreSave(FObjectPreSaveContext SaveContext)
{
Super::PreSave(SaveContext);
}
#endif

View File

@@ -0,0 +1,7 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Abilities/GGA_GameplayAbilityInterface.h"
// Add default functionality here for any IGGA_GroupedAbilityInterface functions that are not pure virtual.

View File

@@ -0,0 +1,43 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "AbilityTasks/GGA_AbilityTask_NetworkSyncPoint.h"
#include "Engine/World.h"
#include "TimerManager.h"
#include "AbilitySystemComponent.h"
UGGA_AbilityTask_NetworkSyncPoint* UGGA_AbilityTask_NetworkSyncPoint::WaitNetSyncWithTimeout(UGameplayAbility* OwningAbility, EAbilityTaskNetSyncType InSyncType, float InTimeout)
{
UGGA_AbilityTask_NetworkSyncPoint* MyObj = NewAbilityTask<UGGA_AbilityTask_NetworkSyncPoint>(OwningAbility);
MyObj->SyncType = InSyncType;
MyObj->Time = InTimeout;
return MyObj;
}
void UGGA_AbilityTask_NetworkSyncPoint::Activate()
{
Super::Activate();
if (TaskState != EGameplayTaskState::Finished && AbilitySystemComponent.IsValid())
{
UWorld* World = GetWorld();
TimeStarted = World->GetTimeSeconds();
if (Time <= 0.0f)
{
World->GetTimerManager().SetTimerForNextTick(this, &ThisClass::OnTimeFinish);
}
else
{
// Use a dummy timer handle as we don't need to store it for later but we don't need to look for something to clear
FTimerHandle TimerHandle;
World->GetTimerManager().SetTimer(TimerHandle, this, &ThisClass::OnTimeFinish, Time, false);
}
}
}
void UGGA_AbilityTask_NetworkSyncPoint::OnTimeFinish()
{
if (ShouldBroadcastAbilityTaskDelegates())
{
SyncFinished();
}
}

View File

@@ -0,0 +1,335 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "AbilityTasks/GGA_AbilityTask_PlayMontageAndWaitForEvent.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystemGlobals.h"
#include "GGA_LogChannels.h"
#include "Animation/AnimMontage.h"
#include "Animation/AnimInstance.h"
#include "GameFramework/Character.h"
static bool GUseAggressivePlayMontageAndWaitEndTask = true;
static FAutoConsoleVariableRef CVarAggressivePlayMontageAndWaitEndTask(
TEXT("GGA.PlayMontage.AggressiveEndTask"), GUseAggressivePlayMontageAndWaitEndTask,
TEXT("This should be set to true in order to avoid multiple callbacks off an GGA_AbilityTask_PlayMontageAndWaitForEvent node"));
static bool GPlayMontageAndWaitFireInterruptOnAnimEndInterrupt = true;
static FAutoConsoleVariableRef CVarPlayMontageAndWaitFireInterruptOnAnimEndInterrupt(
TEXT("GGA.PlayMontage.FireInterruptOnAnimEndInterrupt"), GPlayMontageAndWaitFireInterruptOnAnimEndInterrupt,
TEXT("This is a fix that will cause GGA_AbilityTask_PlayMontageAndWaitForEvent to fire its Interrupt event if the underlying AnimInstance ends in an interrupted"));
UGGA_AbilityTask_PlayMontageAndWaitForEvent::UGGA_AbilityTask_PlayMontageAndWaitForEvent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Rate = 1.f;
bAllowInterruptAfterBlendOut = false;
bStopWhenAbilityEnds = true;
}
void UGGA_AbilityTask_PlayMontageAndWaitForEvent::OnMontageBlendingOut(UAnimMontage* Montage, bool bInterrupted)
{
const bool bPlayingThisMontage = (Montage == MontageToPlay) && Ability && Ability->GetCurrentMontage() == MontageToPlay;
if (bPlayingThisMontage)
{
// Reset AnimRootMotionTranslationScale
ACharacter* Character = Cast<ACharacter>(GetAvatarActor());
if (Character && (Character->GetLocalRole() == ROLE_Authority ||
(Character->GetLocalRole() == ROLE_AutonomousProxy && Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalPredicted)))
{
Character->SetAnimRootMotionTranslationScale(1.f);
}
}
if (bPlayingThisMontage && (bInterrupted || !bAllowInterruptAfterBlendOut))
{
if (UAbilitySystemComponent* ASC = AbilitySystemComponent.Get())
{
ASC->ClearAnimatingAbility(Ability);
}
}
if (ShouldBroadcastAbilityTaskDelegates())
{
if (bInterrupted)
{
bAllowInterruptAfterBlendOut = false;
OnInterrupted.Broadcast(FGameplayTag(), FGameplayEventData());
if (GUseAggressivePlayMontageAndWaitEndTask)
{
EndTask();
}
}
else
{
OnBlendOut.Broadcast(FGameplayTag(), FGameplayEventData());
}
}
}
void UGGA_AbilityTask_PlayMontageAndWaitForEvent::OnMontageBlendedIn(UAnimMontage* Montage)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
OnInterrupted.Broadcast(FGameplayTag(), FGameplayEventData());
}
}
void UGGA_AbilityTask_PlayMontageAndWaitForEvent::OnGameplayAbilityCancelled()
{
if (StopPlayingMontage() || bAllowInterruptAfterBlendOut)
{
// Let the BP handle the interrupt as well
if (ShouldBroadcastAbilityTaskDelegates())
{
OnCancelled.Broadcast(FGameplayTag(), FGameplayEventData());
}
}
if (GUseAggressivePlayMontageAndWaitEndTask)
{
EndTask();
}
}
void UGGA_AbilityTask_PlayMontageAndWaitForEvent::OnMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
if (!bInterrupted)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
OnCompleted.Broadcast(FGameplayTag(), FGameplayEventData());
}
}
else if (bAllowInterruptAfterBlendOut && GUseAggressivePlayMontageAndWaitEndTask)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
OnInterrupted.Broadcast(FGameplayTag(), FGameplayEventData());
}
}
EndTask();
}
void UGGA_AbilityTask_PlayMontageAndWaitForEvent::OnGameplayEvent(FGameplayTag EventTag, const FGameplayEventData* Payload)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
FGameplayEventData TempData = *Payload;
TempData.EventTag = EventTag;
EventReceived.Broadcast(EventTag, TempData);
}
}
UGGA_AbilityTask_PlayMontageAndWaitForEvent* UGGA_AbilityTask_PlayMontageAndWaitForEvent::PlayMontageAndWaitForEvent(UGameplayAbility* OwningAbility,
FName TaskInstanceName, UAnimMontage* MontageToPlay,
FGameplayTagContainer EventTags, float Rate, FName StartSection,
bool bStopWhenAbilityEnds, float AnimRootMotionTranslationScale,
float StartTimeSeconds, bool bAllowInterruptAfterBlendOut)
{
UAbilitySystemGlobals::NonShipping_ApplyGlobalAbilityScaler_Rate(Rate);
UGGA_AbilityTask_PlayMontageAndWaitForEvent* MyObj = NewAbilityTask<UGGA_AbilityTask_PlayMontageAndWaitForEvent>(OwningAbility, TaskInstanceName);
MyObj->MontageToPlay = MontageToPlay;
MyObj->Rate = Rate;
MyObj->StartSection = StartSection;
MyObj->AnimRootMotionTranslationScale = AnimRootMotionTranslationScale;
MyObj->bStopWhenAbilityEnds = bStopWhenAbilityEnds;
MyObj->bAllowInterruptAfterBlendOut = bAllowInterruptAfterBlendOut;
MyObj->StartTimeSeconds = StartTimeSeconds;
MyObj->EventTags = EventTags;
return MyObj;
}
UGGA_AbilityTask_PlayMontageAndWaitForEvent* UGGA_AbilityTask_PlayMontageAndWaitForEvent::PlayMontageAndWaitForEventExt(UGameplayAbility* OwningAbility,
FGGA_PlayMontageAndWaitForEventTaskParams Params)
{
UAbilitySystemGlobals::NonShipping_ApplyGlobalAbilityScaler_Rate(Params.Rate);
UGGA_AbilityTask_PlayMontageAndWaitForEvent* MyObj = NewAbilityTask<UGGA_AbilityTask_PlayMontageAndWaitForEvent>(OwningAbility, Params.TaskInstanceName);
MyObj->MontageToPlay = Params.MontageToPlay;
MyObj->Rate = Params.Rate;
MyObj->StartSection = Params.StartSection;
MyObj->AnimRootMotionTranslationScale = Params.AnimRootMotionTranslationScale;
MyObj->bStopWhenAbilityEnds = Params.bStopWhenAbilityEnds;
MyObj->bAllowInterruptAfterBlendOut = Params.bAllowInterruptAfterBlendOut;
MyObj->StartTimeSeconds = Params.StartTimeSeconds;
MyObj->EventTags = Params.EventTags;
return MyObj;
}
void UGGA_AbilityTask_PlayMontageAndWaitForEvent::Activate()
{
if (Ability == nullptr)
{
return;
}
bool bPlayedMontage = false;
if (UAbilitySystemComponent* ASC = AbilitySystemComponent.Get())
{
const FGameplayAbilityActorInfo* ActorInfo = Ability->GetCurrentActorInfo();
UAnimInstance* AnimInstance = ActorInfo->GetAnimInstance();
if (AnimInstance != nullptr)
{
if (ASC->PlayMontage(Ability, Ability->GetCurrentActivationInfo(), MontageToPlay, Rate, StartSection) > 0.f)
{
// Playing a montage could potentially fire off a callback into game code which could kill this ability! Early out if we are pending kill.
if (ShouldBroadcastAbilityTaskDelegates() == false)
{
return;
}
// Bind to event callback
EventHandle = ASC->AddGameplayEventTagContainerDelegate(
EventTags, FGameplayEventTagMulticastDelegate::FDelegate::CreateUObject(this, &UGGA_AbilityTask_PlayMontageAndWaitForEvent::OnGameplayEvent));
InterruptedHandle = Ability->OnGameplayAbilityCancelled.AddUObject(this, &UGGA_AbilityTask_PlayMontageAndWaitForEvent::OnGameplayAbilityCancelled);
BlendedInDelegate.BindUObject(this, &UGGA_AbilityTask_PlayMontageAndWaitForEvent::OnMontageBlendedIn);
AnimInstance->Montage_SetBlendedInDelegate(BlendedInDelegate, MontageToPlay);
BlendingOutDelegate.BindUObject(this, &UGGA_AbilityTask_PlayMontageAndWaitForEvent::OnMontageBlendingOut);
AnimInstance->Montage_SetBlendingOutDelegate(BlendingOutDelegate, MontageToPlay);
MontageEndedDelegate.BindUObject(this, &UGGA_AbilityTask_PlayMontageAndWaitForEvent::OnMontageEnded);
AnimInstance->Montage_SetEndDelegate(MontageEndedDelegate, MontageToPlay);
ACharacter* Character = Cast<ACharacter>(GetAvatarActor());
if (Character && (Character->GetLocalRole() == ROLE_Authority ||
(Character->GetLocalRole() == ROLE_AutonomousProxy && Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalPredicted)))
{
Character->SetAnimRootMotionTranslationScale(AnimRootMotionTranslationScale);
}
bPlayedMontage = true;
}
}
else
{
UE_LOG(LogGGA_Tasks, Warning, TEXT("GGA_AbilityTask_PlayMontageAndWaitForEvent call to PlayMontage failed!"));
}
}
else
{
UE_LOG(LogGGA_Tasks, Warning, TEXT("GGA_AbilityTask_PlayMontageAndWaitForEvent called on invalid AbilitySystemComponent"));
}
if (!bPlayedMontage)
{
UE_LOG(LogGGA_Tasks, Warning, TEXT("GGA_AbilityTask_PlayMontageAndWaitForEvent called in Ability %s failed to play montage %s; Task Instance Name %s."), *Ability->GetName(),
*GetNameSafe(MontageToPlay), *InstanceName.ToString());
if (ShouldBroadcastAbilityTaskDelegates())
{
OnCancelled.Broadcast(FGameplayTag(), FGameplayEventData());
}
}
SetWaitingOnAvatar();
}
void UGGA_AbilityTask_PlayMontageAndWaitForEvent::ExternalCancel()
{
if (ShouldBroadcastAbilityTaskDelegates())
{
OnCancelled.Broadcast(FGameplayTag(), FGameplayEventData());
}
Super::ExternalCancel();
}
void UGGA_AbilityTask_PlayMontageAndWaitForEvent::OnDestroy(bool AbilityEnded)
{
// Note: Clearing montage end delegate isn't necessary since its not a multicast and will be cleared when the next montage plays.
// (If we are destroyed, it will detect this and not do anything)
// This delegate, however, should be cleared as it is a multicast
if (Ability)
{
Ability->OnGameplayAbilityCancelled.Remove(InterruptedHandle);
if (AbilityEnded && bStopWhenAbilityEnds)
{
StopPlayingMontage();
}
}
if (UAbilitySystemComponent* ASC = AbilitySystemComponent.Get())
{
ASC->RemoveGameplayEventTagContainerDelegate(EventTags, EventHandle);
}
Super::OnDestroy(AbilityEnded);
}
void UGGA_AbilityTask_PlayMontageAndWaitForEvent::EndTaskByOwner()
{
TaskOwnerEnded();
}
bool UGGA_AbilityTask_PlayMontageAndWaitForEvent::StopPlayingMontage()
{
if (Ability == nullptr)
{
return false;
}
const FGameplayAbilityActorInfo* ActorInfo = Ability->GetCurrentActorInfo();
if (ActorInfo == nullptr)
{
return false;
}
UAnimInstance* AnimInstance = ActorInfo->GetAnimInstance();
if (AnimInstance == nullptr)
{
return false;
}
// Check if the montage is still playing
// The ability would have been interrupted, in which case we should automatically stop the montage
UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
if (ASC && Ability)
{
if (ASC->GetAnimatingAbility() == Ability
&& ASC->GetCurrentMontage() == MontageToPlay)
{
// Unbind delegates so they don't get called as well
FAnimMontageInstance* MontageInstance = AnimInstance->GetActiveInstanceForMontage(MontageToPlay);
if (MontageInstance)
{
MontageInstance->OnMontageBlendedInEnded.Unbind();
MontageInstance->OnMontageBlendingOutStarted.Unbind();
MontageInstance->OnMontageEnded.Unbind();
}
ASC->CurrentMontageStop();
return true;
}
}
return false;
}
FString UGGA_AbilityTask_PlayMontageAndWaitForEvent::GetDebugString() const
{
UAnimMontage* PlayingMontage = nullptr;
if (Ability)
{
const FGameplayAbilityActorInfo* ActorInfo = Ability->GetCurrentActorInfo();
UAnimInstance* AnimInstance = ActorInfo->GetAnimInstance();
if (AnimInstance != nullptr)
{
PlayingMontage = AnimInstance->Montage_IsActive(MontageToPlay) ? ToRawPtr(MontageToPlay) : AnimInstance->GetCurrentActiveMontage();
}
}
return FString::Printf(TEXT("PlayMontageAndWaitForEvent. MontageToPlay: %s (Currently Playing): %s"), *GetNameSafe(MontageToPlay), *GetNameSafe(PlayingMontage));
}

View File

@@ -0,0 +1,78 @@
//// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
//
//
//#include "AbilityTasks/GGA_AbilityTask_RunCustomAbilityTask.h"
//
//#include "GameplayTasksComponent.h"
//#include "CustomTasks/GGA_CustomAbilityTask.h"
//#include "Net/UnrealNetwork.h"
//
//UGGA_AbilityTask_RunCustomAbilityTask::UGGA_AbilityTask_RunCustomAbilityTask()
//{
//}
//
//void UGGA_AbilityTask_RunCustomAbilityTask::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
//{
// Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// DOREPLIFETIME(UGGA_AbilityTask_RunCustomAbilityTask, TaskInstance);
//}
//
//UGGA_AbilityTask_RunCustomAbilityTask* UGGA_AbilityTask_RunCustomAbilityTask::RunCustomAbilityTask(UGameplayAbility* OwningAbility, TSoftClassPtr<UGGA_CustomAbilityTask> AbilityTaskClass)
//{
// if (TSubclassOf<UGGA_CustomAbilityTask> RealClass = AbilityTaskClass.LoadSynchronous())
// {
// UGGA_AbilityTask_RunCustomAbilityTask* MyObj = NewAbilityTask<UGGA_AbilityTask_RunCustomAbilityTask>(OwningAbility);
//
// TObjectPtr<UGGA_CustomAbilityTask> CustomAbilityTask = NewObject<UGGA_CustomAbilityTask>(MyObj, RealClass);
// CustomAbilityTask->SetSourceTask(MyObj);
// MyObj->bSimulatedTask = CustomAbilityTask->bSimulatedTask;
// MyObj->bTickingTask = CustomAbilityTask->bTickingTask;
// return MyObj;
// }
// return nullptr;
//}
//
//void UGGA_AbilityTask_RunCustomAbilityTask::Activate()
//{
// if (UGameplayTasksComponent* Component = GetGameplayTasksComponent())
// {
// if (IsSimulatedTask())
// {
// if (Component->IsUsingRegisteredSubObjectList() && Component->IsReadyForReplication())
// {
// Component->AddReplicatedSubObject(TaskInstance, COND_SkipOwner);
// }
// }
// }
// TaskInstance->OnTaskActivate();
//}
//
//void UGGA_AbilityTask_RunCustomAbilityTask::TickTask(float DeltaTime)
//{
// TaskInstance->OnTaskTick(DeltaTime);
//}
//
//void UGGA_AbilityTask_RunCustomAbilityTask::OnDestroy(bool bInOwnerFinished)
//{
// if (!bWasSuccessfullyDestroyed)
// {
// if (UGameplayTasksComponent* Component = GetGameplayTasksComponent())
// {
// if (IsSimulatedTask())
// {
// if (Component->IsUsingRegisteredSubObjectList())
// {
// Component->RemoveReplicatedSubObject(TaskInstance);
// }
// }
// }
// TaskInstance->OnTaskDestroy(bInOwnerFinished);
// }
// Super::OnDestroy(bInOwnerFinished);
//}
//
//void UGGA_AbilityTask_RunCustomAbilityTask::InitSimulatedTask(UGameplayTasksComponent& InGameplayTasksComponent)
//{
// Super::InitSimulatedTask(InGameplayTasksComponent);
// TaskInstance->OnInitSimulatedTask();
//}

View File

@@ -0,0 +1,59 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "AbilityTasks/GGA_AbilityTask_ServerWaitForClientTargetData.h"
#include "AbilitySystemComponent.h"
UGGA_AbilityTask_ServerWaitForClientTargetData::UGGA_AbilityTask_ServerWaitForClientTargetData(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
UGGA_AbilityTask_ServerWaitForClientTargetData* UGGA_AbilityTask_ServerWaitForClientTargetData::ServerWaitForClientTargetData(UGameplayAbility* OwningAbility, FName TaskInstanceName, bool TriggerOnce)
{
UGGA_AbilityTask_ServerWaitForClientTargetData* MyObj = NewAbilityTask<UGGA_AbilityTask_ServerWaitForClientTargetData>(OwningAbility, TaskInstanceName);
MyObj->bTriggerOnce = TriggerOnce;
return MyObj;
}
void UGGA_AbilityTask_ServerWaitForClientTargetData::Activate()
{
// ClientPath
if (!Ability || !Ability->GetCurrentActorInfo()->IsNetAuthority())
{
return;
}
// ServerPath
FGameplayAbilitySpecHandle SpecHandle = GetAbilitySpecHandle();
FPredictionKey ActivationPredictionKey = GetActivationPredictionKey();
AbilitySystemComponent->AbilityTargetDataSetDelegate(SpecHandle, ActivationPredictionKey).AddUObject(this, &UGGA_AbilityTask_ServerWaitForClientTargetData::OnTargetDataReplicatedCallback);
}
void UGGA_AbilityTask_ServerWaitForClientTargetData::OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& Data, FGameplayTag ActivationTag)
{
FGameplayAbilityTargetDataHandle MutableData = Data;
AbilitySystemComponent->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey());
if (ShouldBroadcastAbilityTaskDelegates())
{
ValidData.Broadcast(MutableData);
}
if (bTriggerOnce)
{
EndTask();
}
}
void UGGA_AbilityTask_ServerWaitForClientTargetData::OnDestroy(bool AbilityEnded)
{
if (AbilitySystemComponent.IsValid())
{
FGameplayAbilitySpecHandle SpecHandle = GetAbilitySpecHandle();
FPredictionKey ActivationPredictionKey = GetActivationPredictionKey();
AbilitySystemComponent->AbilityTargetDataSetDelegate(SpecHandle, ActivationPredictionKey).RemoveAll(this);
}
Super::OnDestroy(AbilityEnded);
}

View File

@@ -0,0 +1,31 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "AbilityTasks/GGA_AbilityTask_WaitDelayOneFrame.h"
#include "Engine/World.h"
#include "TimerManager.h"
UGGA_AbilityTask_WaitDelayOneFrame::UGGA_AbilityTask_WaitDelayOneFrame(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UGGA_AbilityTask_WaitDelayOneFrame::Activate()
{
GetWorld()->GetTimerManager().SetTimerForNextTick(this, &UGGA_AbilityTask_WaitDelayOneFrame::OnDelayFinish);
}
UGGA_AbilityTask_WaitDelayOneFrame* UGGA_AbilityTask_WaitDelayOneFrame::WaitDelayOneFrame(UGameplayAbility* OwningAbility)
{
UGGA_AbilityTask_WaitDelayOneFrame* MyObj = NewAbilityTask<UGGA_AbilityTask_WaitDelayOneFrame>(OwningAbility);
return MyObj;
}
void UGGA_AbilityTask_WaitDelayOneFrame::OnDelayFinish()
{
if (ShouldBroadcastAbilityTaskDelegates())
{
OnFinish.Broadcast();
}
EndTask();
}

View File

@@ -0,0 +1,82 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "AbilityTasks/GGA_AbilityTask_WaitGameplayEvents.h"
#include "AbilitySystemGlobals.h"
#include "AbilitySystemComponent.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GGA_AbilityTask_WaitGameplayEvents)
// ----------------------------------------------------------------
UGGA_AbilityTask_WaitGameplayEvents::UGGA_AbilityTask_WaitGameplayEvents(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
UGGA_AbilityTask_WaitGameplayEvents* UGGA_AbilityTask_WaitGameplayEvents::WaitGameplayEvents(UGameplayAbility* OwningAbility, FGameplayTagContainer EventTags, AActor* OptionalExternalTarget,
bool OnlyTriggerOnce)
{
UGGA_AbilityTask_WaitGameplayEvents* MyObj = NewAbilityTask<UGGA_AbilityTask_WaitGameplayEvents>(OwningAbility);
MyObj->EventTags = EventTags;
MyObj->SetExternalTarget(OptionalExternalTarget);
MyObj->OnlyTriggerOnce = OnlyTriggerOnce;
return MyObj;
}
void UGGA_AbilityTask_WaitGameplayEvents::Activate()
{
UAbilitySystemComponent* ASC = GetTargetASC();
if (ASC)
{
MyHandle = ASC->AddGameplayEventTagContainerDelegate(
EventTags, FGameplayEventTagMulticastDelegate::FDelegate::CreateUObject(this, &UGGA_AbilityTask_WaitGameplayEvents::GameplayEventContainerCallback));
}
Super::Activate();
}
void UGGA_AbilityTask_WaitGameplayEvents::GameplayEventContainerCallback(FGameplayTag MatchingTag, const FGameplayEventData* Payload)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
ensureMsgf(Payload, TEXT("GameplayEventCallback expected non-null Payload"));
FGameplayEventData TempPayload = Payload ? *Payload : FGameplayEventData{};
TempPayload.EventTag = MatchingTag;
EventReceived.Broadcast(MatchingTag, TempPayload);
}
if (OnlyTriggerOnce)
{
EndTask();
}
}
void UGGA_AbilityTask_WaitGameplayEvents::SetExternalTarget(AActor* Actor)
{
if (Actor)
{
UseExternalTarget = true;
OptionalExternalTarget = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(Actor);
}
}
UAbilitySystemComponent* UGGA_AbilityTask_WaitGameplayEvents::GetTargetASC()
{
if (UseExternalTarget)
{
return OptionalExternalTarget;
}
return AbilitySystemComponent.Get();
}
void UGGA_AbilityTask_WaitGameplayEvents::OnDestroy(bool AbilityEnding)
{
UAbilitySystemComponent* ASC = GetTargetASC();
if (ASC && MyHandle.IsValid())
{
ASC->RemoveGameplayEventTagContainerDelegate(EventTags, MyHandle);
}
Super::OnDestroy(AbilityEnding);
}

View File

@@ -0,0 +1,126 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "AbilityTasks/GGA_AbilityTask_WaitInputPressWithTags.h"
#include "Engine/World.h"
#include "AbilitySystemComponent.h"
UGGA_AbilityTask_WaitInputPressWithTags::UGGA_AbilityTask_WaitInputPressWithTags(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
StartTime = 0.f;
bTestInitialState = false;
}
UGGA_AbilityTask_WaitInputPressWithTags* UGGA_AbilityTask_WaitInputPressWithTags::WaitInputPressWithTags(UGameplayAbility* OwningAbility, FGameplayTagContainer RequiredTags,
FGameplayTagContainer IgnoredTags,
bool bTestAlreadyPressed)
{
UGGA_AbilityTask_WaitInputPressWithTags* Task = NewAbilityTask<UGGA_AbilityTask_WaitInputPressWithTags>(OwningAbility);
Task->bTestInitialState = bTestAlreadyPressed;
Task->TagQuery = FGameplayTagQuery::BuildQuery(FGameplayTagQueryExpression().AllTagsMatch().AddTags(RequiredTags).NoTagsMatch().AddTags(IgnoredTags));
return Task;
}
UGGA_AbilityTask_WaitInputPressWithTags* UGGA_AbilityTask_WaitInputPressWithTags::WaitInputPressWithTagQuery(UGameplayAbility* OwningAbility, const FGameplayTagQuery& TagQuery,
bool bTestAlreadyPressed)
{
UGGA_AbilityTask_WaitInputPressWithTags* Task = NewAbilityTask<UGGA_AbilityTask_WaitInputPressWithTags>(OwningAbility);
Task->bTestInitialState = bTestAlreadyPressed;
Task->TagQuery = TagQuery;
return Task;
}
void UGGA_AbilityTask_WaitInputPressWithTags::OnPressCallback()
{
float ElapsedTime = GetWorld()->GetTimeSeconds() - StartTime;
UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
if (!Ability || !ASC)
{
EndTask();
return;
}
const FGameplayTagContainer CurrentTags = ASC->GetOwnedGameplayTags();
if (!TagQuery.Matches(CurrentTags))
{
Reset();
return;
}
ASC->AbilityReplicatedEventDelegate(EAbilityGenericReplicatedEvent::InputPressed, GetAbilitySpecHandle(), GetActivationPredictionKey()).Remove(DelegateHandle);
FScopedPredictionWindow ScopedPrediction(ASC, IsPredictingClient());
if (IsPredictingClient())
{
// Tell the server about this
ASC->ServerSetReplicatedEvent(EAbilityGenericReplicatedEvent::InputPressed, GetAbilitySpecHandle(), GetActivationPredictionKey(),
ASC->ScopedPredictionKey);
}
else
{
ASC->ConsumeGenericReplicatedEvent(EAbilityGenericReplicatedEvent::InputPressed, GetAbilitySpecHandle(), GetActivationPredictionKey());
}
// We are done. Kill us so we don't keep getting broadcast messages
if (ShouldBroadcastAbilityTaskDelegates())
{
OnPress.Broadcast(ElapsedTime);
}
EndTask();
}
void UGGA_AbilityTask_WaitInputPressWithTags::Activate()
{
StartTime = GetWorld()->GetTimeSeconds();
if (Ability)
{
if (bTestInitialState && IsLocallyControlled())
{
FGameplayAbilitySpec* Spec = Ability->GetCurrentAbilitySpec();
if (Spec && Spec->InputPressed)
{
OnPressCallback();
return;
}
}
DelegateHandle = AbilitySystemComponent->AbilityReplicatedEventDelegate(EAbilityGenericReplicatedEvent::InputPressed, GetAbilitySpecHandle(), GetActivationPredictionKey()).AddUObject(
this, &UGGA_AbilityTask_WaitInputPressWithTags::OnPressCallback);
if (IsForRemoteClient())
{
if (!AbilitySystemComponent->CallReplicatedEventDelegateIfSet(EAbilityGenericReplicatedEvent::InputPressed, GetAbilitySpecHandle(), GetActivationPredictionKey()))
{
SetWaitingOnRemotePlayerData();
}
}
}
}
void UGGA_AbilityTask_WaitInputPressWithTags::OnDestroy(bool AbilityEnded)
{
AbilitySystemComponent->AbilityReplicatedEventDelegate(EAbilityGenericReplicatedEvent::InputPressed, GetAbilitySpecHandle(), GetActivationPredictionKey()).Remove(DelegateHandle);
ClearWaitingOnRemotePlayerData();
Super::OnDestroy(AbilityEnded);
}
void UGGA_AbilityTask_WaitInputPressWithTags::Reset()
{
AbilitySystemComponent->AbilityReplicatedEventDelegate(EAbilityGenericReplicatedEvent::InputPressed, GetAbilitySpecHandle(), GetActivationPredictionKey()).Remove(DelegateHandle);
DelegateHandle = AbilitySystemComponent->AbilityReplicatedEventDelegate(EAbilityGenericReplicatedEvent::InputPressed, GetAbilitySpecHandle(), GetActivationPredictionKey()).AddUObject(
this, &UGGA_AbilityTask_WaitInputPressWithTags::OnPressCallback);
if (IsForRemoteClient())
{
if (!AbilitySystemComponent->CallReplicatedEventDelegateIfSet(EAbilityGenericReplicatedEvent::InputPressed, GetAbilitySpecHandle(), GetActivationPredictionKey()))
{
SetWaitingOnRemotePlayerData();
}
}
}

View File

@@ -0,0 +1,325 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "AbilityTasks/GGA_AbilityTask_WaitTargetDataUsingActor.h"
#include "AbilitySystemComponent.h"
#include "TargetActors/GGA_AbilityTargetActor_Trace.h"
UGGA_AbilityTask_WaitTargetDataUsingActor* UGGA_AbilityTask_WaitTargetDataUsingActor::WaitTargetDataWithReusableActor(
UGameplayAbility* OwningAbility, FName TaskInstanceName,
TEnumAsByte<EGameplayTargetingConfirmation::Type> ConfirmationType, AGameplayAbilityTargetActor* InTargetActor,
bool bCreateKeyIfNotValidForMorePrediction)
{
UGGA_AbilityTask_WaitTargetDataUsingActor* MyObj = NewAbilityTask<UGGA_AbilityTask_WaitTargetDataUsingActor>(
OwningAbility, TaskInstanceName); //Register for task list here, providing a given FName as a key
MyObj->TargetActor = InTargetActor;
MyObj->ConfirmationType = ConfirmationType;
MyObj->bCreateKeyIfNotValidForMorePrediction = bCreateKeyIfNotValidForMorePrediction;
return MyObj;
}
void UGGA_AbilityTask_WaitTargetDataUsingActor::Activate()
{
if (!IsValid(this))
{
return;
}
if (Ability && TargetActor)
{
/** server&client 注册TargetActor上的Ready(Confirm)/Cancel事件 */
InitializeTargetActor();
/** server注册TargetDeta */
RegisterTargetDataCallbacks();
FinalizeTargetActor();
}
else
{
EndTask();
}
}
void UGGA_AbilityTask_WaitTargetDataUsingActor::OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& Data,
FGameplayTag ActivationTag)
{
FGameplayAbilityTargetDataHandle MutableData = Data;
if (UAbilitySystemComponent* ASC = AbilitySystemComponent.Get())
{
AbilitySystemComponent->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey());
}
/**
* Call into the TargetActor to sanitize/verify the data. If this returns false, we are rejecting
* the replicated target data and will treat this as a cancel.
*
* This can also be used for bandwidth optimizations. OnReplicatedTargetDataReceived could do an actual
* trace/check/whatever server side and use that data. So rather than having the client send that data
* explicitly, the client is basically just sending a 'confirm' and the server is now going to do the work
* in OnReplicatedTargetDataReceived.
*/
if (TargetActor && !TargetActor->OnReplicatedTargetDataReceived(MutableData))
{
if (ShouldBroadcastAbilityTaskDelegates())
{
Cancelled.Broadcast(MutableData);
}
}
else
{
if (ShouldBroadcastAbilityTaskDelegates())
{
ValidData.Broadcast(MutableData);
}
}
if (ConfirmationType != EGameplayTargetingConfirmation::CustomMulti)
{
EndTask();
}
}
void UGGA_AbilityTask_WaitTargetDataUsingActor::OnTargetDataReplicatedCancelledCallback()
{
if (ShouldBroadcastAbilityTaskDelegates())
{
Cancelled.Broadcast(FGameplayAbilityTargetDataHandle());
}
EndTask();
}
void UGGA_AbilityTask_WaitTargetDataUsingActor::OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& Data)
{
UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
if (!Ability || !ASC)
{
return;
}
// client path
FScopedPredictionWindow ScopedPrediction(ASC,
ShouldReplicateDataToServer() && (bCreateKeyIfNotValidForMorePrediction &&
!ASC->ScopedPredictionKey.IsValidForMorePrediction()
));
const FGameplayAbilityActorInfo* Info = Ability->GetCurrentActorInfo();
// client path
if (IsPredictingClient())
{
// Rpc发送TargetData到服务器
if (!TargetActor->ShouldProduceTargetDataOnServer)
{
FGameplayTag ApplicationTag; // Fixme: where would this be useful?
ASC->CallServerSetReplicatedTargetData(GetAbilitySpecHandle(),
GetActivationPredictionKey(), Data,
ApplicationTag,
AbilitySystemComponent->ScopedPredictionKey);
}
else if (ConfirmationType == EGameplayTargetingConfirmation::UserConfirmed)
{
// Rpc告诉服务器确认了。
// We aren't going to send the target data, but we will send a generic confirmed message.
ASC->ServerSetReplicatedEvent(EAbilityGenericReplicatedEvent::GenericConfirm,
GetAbilitySpecHandle(), GetActivationPredictionKey(),
AbilitySystemComponent->ScopedPredictionKey);
}
}
if (ShouldBroadcastAbilityTaskDelegates())
{
ValidData.Broadcast(Data);
}
if (ConfirmationType != EGameplayTargetingConfirmation::CustomMulti)
{
EndTask();
}
}
void UGGA_AbilityTask_WaitTargetDataUsingActor::OnTargetDataCancelledCallback(const FGameplayAbilityTargetDataHandle& Data)
{
UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
if(!ASC)
{
return;
}
//client path
FScopedPredictionWindow ScopedPrediction(ASC, IsPredictingClient());
//client path
if (IsPredictingClient())
{
if (!TargetActor->ShouldProduceTargetDataOnServer)
{
ASC->ServerSetReplicatedTargetDataCancelled(
GetAbilitySpecHandle(), GetActivationPredictionKey(), ASC->ScopedPredictionKey);
}
else
{
// We aren't going to send the target data, but we will send a generic confirmed message.
ASC->ServerSetReplicatedEvent(EAbilityGenericReplicatedEvent::GenericCancel,
GetAbilitySpecHandle(), GetActivationPredictionKey(),
ASC->ScopedPredictionKey);
}
}
// client&& server path.
Cancelled.Broadcast(Data);
EndTask();
}
void UGGA_AbilityTask_WaitTargetDataUsingActor::ExternalConfirm(bool bEndTask)
{
if (TargetActor)
{
if (TargetActor->ShouldProduceTargetData())
{
TargetActor->ConfirmTargetingAndContinue();
}
}
Super::ExternalConfirm(bEndTask);
}
void UGGA_AbilityTask_WaitTargetDataUsingActor::ExternalCancel()
{
if (ShouldBroadcastAbilityTaskDelegates())
{
Cancelled.Broadcast(FGameplayAbilityTargetDataHandle());
}
Super::ExternalCancel();
}
void UGGA_AbilityTask_WaitTargetDataUsingActor::InitializeTargetActor() const
{
check(TargetActor);
check(Ability);
TargetActor->PrimaryPC = Ability->GetCurrentActorInfo()->PlayerController.Get();
TargetActor->TargetDataReadyDelegate.AddUObject(
const_cast<UGGA_AbilityTask_WaitTargetDataUsingActor*>(this), &UGGA_AbilityTask_WaitTargetDataUsingActor::OnTargetDataReadyCallback);
TargetActor->CanceledDelegate.AddUObject(
const_cast<UGGA_AbilityTask_WaitTargetDataUsingActor*>(this), &UGGA_AbilityTask_WaitTargetDataUsingActor::OnTargetDataCancelledCallback);
}
void UGGA_AbilityTask_WaitTargetDataUsingActor::RegisterTargetDataCallbacks()
{
if (!ensure(IsValid(this) == true))
{
return;
}
UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
if (!ASC)
{
return;
}
check(Ability);
const bool bIsLocalControlled = Ability->GetCurrentActorInfo()->IsLocallyControlled();
const bool bShouldProduceTargetDataOnServer = TargetActor->ShouldProduceTargetDataOnServer;
/** server path. 若不是本地控制的(server for remote client),查看TargetData是否已发送否则在到达此处时注册回调 */
if (!bIsLocalControlled)
{
//如果我们希望客户端发送TargetData回调就注册TargetData回调
if (!bShouldProduceTargetDataOnServer) // produce on client
{
FGameplayAbilitySpecHandle SpecHandle = GetAbilitySpecHandle();
FPredictionKey ActivationPredictionKey = GetActivationPredictionKey();
/** 注册TargetDataSet事件 */
ASC->AbilityTargetDataSetDelegate(SpecHandle, ActivationPredictionKey).AddUObject(
this, &UGGA_AbilityTask_WaitTargetDataUsingActor::OnTargetDataReplicatedCallback);
/** 注册TargetDataCancel事件*/
ASC->AbilityTargetDataCancelledDelegate(SpecHandle, ActivationPredictionKey).AddUObject(
this, &UGGA_AbilityTask_WaitTargetDataUsingActor::OnTargetDataReplicatedCancelledCallback);
// 检查TargetData是否已经Confirm/Cancel并执行相关操作。
ASC->CallReplicatedTargetDataDelegatesIfSet(SpecHandle, ActivationPredictionKey);
SetWaitingOnRemotePlayerData();
}
}
}
void UGGA_AbilityTask_WaitTargetDataUsingActor::FinalizeTargetActor() const
{
check(TargetActor);
check(Ability);
TargetActor->StartTargeting(Ability);
if (TargetActor->ShouldProduceTargetData())
{
// If instant confirm, then stop targeting immediately.
// Note this is kind of bad: we should be able to just call a static func on the CDO to do this.
// But then we wouldn't get to set ExposeOnSpawnParameters.
if (ConfirmationType == EGameplayTargetingConfirmation::Instant)
{
TargetActor->ConfirmTargeting();
}
else if (ConfirmationType == EGameplayTargetingConfirmation::UserConfirmed)
{
// Bind to the Cancel/Confirm Delegates (called from local confirm or from repped confirm)
TargetActor->BindToConfirmCancelInputs();
}
}
}
void UGGA_AbilityTask_WaitTargetDataUsingActor::OnDestroy(bool AbilityEnded)
{
if (TargetActor)
{
AGGA_AbilityTargetActor_Trace* TraceTargetActor = Cast<AGGA_AbilityTargetActor_Trace>(TargetActor);
if (TraceTargetActor)
{
// TargetActor 基类没有StopTracing函数.
TraceTargetActor->StopTargeting();
}
else
{
// TargetActor doesn't have a StopTargeting function
TargetActor->SetActorTickEnabled(false);
// Clear added callbacks
TargetActor->TargetDataReadyDelegate.RemoveAll(this);
TargetActor->CanceledDelegate.RemoveAll(this);
AbilitySystemComponent->GenericLocalConfirmCallbacks.RemoveDynamic(
TargetActor, &AGameplayAbilityTargetActor::ConfirmTargeting);
AbilitySystemComponent->GenericLocalCancelCallbacks.RemoveDynamic(
TargetActor, &AGameplayAbilityTargetActor::CancelTargeting);
TargetActor->GenericDelegateBoundASC = nullptr;
}
}
Super::OnDestroy(AbilityEnded);
}
bool UGGA_AbilityTask_WaitTargetDataUsingActor::ShouldReplicateDataToServer() const
{
if (!Ability || !TargetActor)
{
return false;
}
const FGameplayAbilityActorInfo* Info = Ability->GetCurrentActorInfo();
if (!Info->IsNetAuthority() && !TargetActor->ShouldProduceTargetDataOnServer)
{
return true;
}
return false;
}

View File

@@ -0,0 +1,86 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "AsyncTasks/GGA_AsyncTask_AttributeChanged.h"
UGGA_AsyncTask_AttributeChanged* UGGA_AsyncTask_AttributeChanged::ListenForAttributeChange(UAbilitySystemComponent* AbilitySystemComponent, FGameplayAttribute Attribute)
{
if (!IsValid(AbilitySystemComponent) || !Attribute.IsValid())
{
return nullptr;
}
UGGA_AsyncTask_AttributeChanged* WaitForAttributeChangedTask = NewObject<UGGA_AsyncTask_AttributeChanged>();
WaitForAttributeChangedTask->SetAbilityActor(AbilitySystemComponent->GetAvatarActor());
WaitForAttributeChangedTask->AttributeToListenFor = Attribute;
return WaitForAttributeChangedTask;
}
UGGA_AsyncTask_AttributeChanged* UGGA_AsyncTask_AttributeChanged::ListenForAttributesChange(UAbilitySystemComponent* AbilitySystemComponent, TArray<FGameplayAttribute> Attributes)
{
if (!IsValid(AbilitySystemComponent) || Attributes.IsEmpty())
{
return nullptr;
}
UGGA_AsyncTask_AttributeChanged* WaitForAttributeChangedTask = NewObject<UGGA_AsyncTask_AttributeChanged>();
WaitForAttributeChangedTask->SetAbilityActor(AbilitySystemComponent->GetAvatarActor());
WaitForAttributeChangedTask->AttributesToListenFor = Attributes;
return WaitForAttributeChangedTask;
}
void UGGA_AsyncTask_AttributeChanged::EndTask()
{
EndAction();
}
void UGGA_AsyncTask_AttributeChanged::Activate()
{
Super::Activate();
if (UAbilitySystemComponent* ASC = GetAbilitySystemComponent())
{
if (AttributeToListenFor.IsValid())
{
ASC->GetGameplayAttributeValueChangeDelegate(AttributeToListenFor).AddUObject(this, &ThisClass::AttributeChanged);
}
for (const FGameplayAttribute& Attribute : AttributesToListenFor)
{
if (Attribute.IsValid())
{
ASC->GetGameplayAttributeValueChangeDelegate(Attribute).AddUObject(this, &ThisClass::AttributeChanged);
}
}
}
else
{
EndAction();
}
}
void UGGA_AsyncTask_AttributeChanged::EndAction()
{
if (UAbilitySystemComponent* ASC = GetAbilitySystemComponent())
{
if (AttributeToListenFor.IsValid())
{
ASC->GetGameplayAttributeValueChangeDelegate(AttributeToListenFor).RemoveAll(this);
}
for (FGameplayAttribute Attribute : AttributesToListenFor)
{
if (AttributeToListenFor.IsValid())
{
ASC->GetGameplayAttributeValueChangeDelegate(Attribute).RemoveAll(this);
}
}
}
Super::EndAction();
}
void UGGA_AsyncTask_AttributeChanged::AttributeChanged(const FOnAttributeChangeData& Data)
{
OnAttributeChanged.Broadcast(Data.Attribute, Data.NewValue, Data.OldValue);
}

View File

@@ -0,0 +1,60 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "AsyncTasks/GGA_AsyncTask_GameplayTagAddedRemoved.h"
UGGA_AsyncTask_GameplayTagAddedRemoved* UGGA_AsyncTask_GameplayTagAddedRemoved::ListenForGameplayTagAddedOrRemoved(UAbilitySystemComponent* AbilitySystemComponent, FGameplayTagContainer InTags)
{
UGGA_AsyncTask_GameplayTagAddedRemoved* TaskInstance = NewObject<UGGA_AsyncTask_GameplayTagAddedRemoved>();
TaskInstance->SetAbilitySystemComponent(AbilitySystemComponent);
TaskInstance->Tags = InTags;
if (!IsValid(AbilitySystemComponent) || InTags.Num() < 1)
{
TaskInstance->EndTask();
return nullptr;
}
TArray<FGameplayTag> TagArray;
InTags.GetGameplayTagArray(TagArray);
for (FGameplayTag Tag : TagArray)
{
AbilitySystemComponent->RegisterGameplayTagEvent(Tag, EGameplayTagEventType::NewOrRemoved).AddUObject(TaskInstance, &UGGA_AsyncTask_GameplayTagAddedRemoved::TagChanged);
}
return TaskInstance;
}
void UGGA_AsyncTask_GameplayTagAddedRemoved::EndTask()
{
EndAction();
}
void UGGA_AsyncTask_GameplayTagAddedRemoved::EndAction()
{
if (UAbilitySystemComponent* ASC = GetAbilitySystemComponent())
{
TArray<FGameplayTag> TagArray;
Tags.GetGameplayTagArray(TagArray);
for (FGameplayTag Tag : TagArray)
{
ASC->RegisterGameplayTagEvent(Tag, EGameplayTagEventType::NewOrRemoved).RemoveAll(this);
}
}
Super::EndAction();
}
void UGGA_AsyncTask_GameplayTagAddedRemoved::TagChanged(const FGameplayTag Tag, int32 NewCount)
{
if (NewCount > 0)
{
OnTagAdded.Broadcast(Tag);
}
else
{
OnTagRemoved.Broadcast(Tag);
}
}

View File

@@ -0,0 +1,58 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "AsyncTasks/GGA_AsyncTask_WaitGameplayAbilityActivated.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemComponent.h"
UGGA_AsyncTask_WaitGameplayAbilityActivated* UGGA_AsyncTask_WaitGameplayAbilityActivated::WaitGameplayAbilityActivated(AActor* TargetActor)
{
UGGA_AsyncTask_WaitGameplayAbilityActivated* MyObj = NewObject<UGGA_AsyncTask_WaitGameplayAbilityActivated>();
MyObj->SetAbilityActor(TargetActor);
MyObj->SetAbilitySystemComponent(UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TargetActor));
return MyObj;
}
void UGGA_AsyncTask_WaitGameplayAbilityActivated::HandleAbilityActivated(UGameplayAbility* Ability)
{
if (ShouldBroadcastDelegates())
{
OnAbilityActivated.Broadcast(Ability);
}
else
{
EndAction();
}
}
bool UGGA_AsyncTask_WaitGameplayAbilityActivated::ShouldBroadcastDelegates() const
{
return Super::ShouldBroadcastDelegates();
}
void UGGA_AsyncTask_WaitGameplayAbilityActivated::Activate()
{
Super::Activate();
if (UAbilitySystemComponent* ASC = GetAbilitySystemComponent())
{
DelegateHandle = ASC->AbilityActivatedCallbacks.AddUObject(this, &UGGA_AsyncTask_WaitGameplayAbilityActivated::HandleAbilityActivated);
}
else
{
EndAction();
}
}
void UGGA_AsyncTask_WaitGameplayAbilityActivated::EndAction()
{
if (UAbilitySystemComponent* ASC = GetAbilitySystemComponent())
{
if (DelegateHandle.IsValid())
{
ASC->AbilityActivatedCallbacks.Remove(DelegateHandle);
}
}
Super::EndAction();
}

View File

@@ -0,0 +1,79 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "AsyncTasks/GGA_AsyncTask_WaitGameplayAbilityEnded.h"
#include "Runtime/Launch/Resources/Version.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemComponent.h"
UGGA_AsyncTask_WaitGameplayAbilityEnded* UGGA_AsyncTask_WaitGameplayAbilityEnded::WaitGameplayAbilityEnded(AActor* TargetActor,
FGameplayTagQuery AbilityQuery)
{
UGGA_AsyncTask_WaitGameplayAbilityEnded* MyObj = NewObject<UGGA_AsyncTask_WaitGameplayAbilityEnded>();
MyObj->SetAbilityActor(TargetActor);
MyObj->SetAbilitySystemComponent(UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TargetActor));
MyObj->AbilityQuery = AbilityQuery;
return MyObj;
}
UGGA_AsyncTask_WaitGameplayAbilityEnded* UGGA_AsyncTask_WaitGameplayAbilityEnded::WaitAbilitySpecHandleEnded(AActor* TargetActor, FGameplayAbilitySpecHandle AbilitySpecHandle)
{
UGGA_AsyncTask_WaitGameplayAbilityEnded* MyObj = NewObject<UGGA_AsyncTask_WaitGameplayAbilityEnded>();
MyObj->SetAbilityActor(TargetActor);
MyObj->SetAbilitySystemComponent(UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TargetActor));
MyObj->AbilitySpecHandle = AbilitySpecHandle;
return MyObj;
}
void UGGA_AsyncTask_WaitGameplayAbilityEnded::HandleAbilityEnded(const FAbilityEndedData& Data)
{
if (ShouldBroadcastDelegates())
{
if (!AbilityQuery.IsEmpty())
{
#if ENGINE_MINOR_VERSION > 4
if (AbilityQuery.Matches(Data.AbilityThatEnded->GetAssetTags()))
#else
if (AbilityQuery.Matches(Data.AbilityThatEnded->AbilityTags))
#endif
{
OnAbilityEnded.Broadcast(Data);
}
}
if (AbilitySpecHandle.IsValid() && AbilitySpecHandle == Data.AbilitySpecHandle)
{
OnAbilityEnded.Broadcast(Data);
}
}
else
{
EndAction();
}
}
void UGGA_AsyncTask_WaitGameplayAbilityEnded::Activate()
{
Super::Activate();
if (UAbilitySystemComponent* ASC = GetAbilitySystemComponent())
{
DelegateHandle = ASC->OnAbilityEnded.AddUObject(this, &UGGA_AsyncTask_WaitGameplayAbilityEnded::HandleAbilityEnded);
}
else
{
EndAction();
}
}
void UGGA_AsyncTask_WaitGameplayAbilityEnded::EndAction()
{
if (UAbilitySystemComponent* ASC = GetAbilitySystemComponent())
{
if (DelegateHandle.IsValid())
{
ASC->AbilityEndedCallbacks.Remove(DelegateHandle);
}
}
Super::EndAction();
}

View File

@@ -0,0 +1,21 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Attributes/GGA_AttributeSet.h"
#include "GGA_AbilitySystemComponent.h"
UGGA_AttributeSet::UGGA_AttributeSet()
{
}
UWorld* UGGA_AttributeSet::GetWorld() const
{
const UObject* Outer = GetOuter();
check(Outer);
return Outer->GetWorld();
}
UGGA_AbilitySystemComponent* UGGA_AttributeSet::GetGGA_AbilitySystemComponent() const
{
return Cast<UGGA_AbilitySystemComponent>(GetOwningAbilitySystemComponent());
}

View File

@@ -0,0 +1,591 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GGA_AbilitySystemComponent.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemLog.h"
#include "GameplayCueManager.h"
#include "GGA_AbilitySystemGlobals.h"
#include "GGA_AbilityTagRelationshipMapping.h"
#include "GGA_GlobalAbilitySystem.h"
#include "GGA_LogChannels.h"
#include "Abilities/GGA_GameplayAbilityInterface.h"
#include "GameFramework/Pawn.h"
#include "Runtime/Launch/Resources/Version.h"
#pragma region Initialization
UGGA_AbilitySystemComponent::UGGA_AbilitySystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
SetIsReplicatedByDefault(true);
AbilitySystemReplicationMode = EGameplayEffectReplicationMode::Mixed;
bReplicateUsingRegisteredSubObjectList = true;
FMemory::Memset(ActivationGroupCounts, 0, sizeof(ActivationGroupCounts));
}
void UGGA_AbilitySystemComponent::InitializeAbilitySystem(AActor* InOwnerActor, AActor* InAvatarActor)
{
check(InOwnerActor);
check(InAvatarActor);
if (!bAbilitySystemInitialized)
{
FGameplayAbilityActorInfo* ActorInfo = AbilityActorInfo.Get();
const bool AvatarChanged = InAvatarActor && (InAvatarActor != ActorInfo->AvatarActor);
InitAbilityActorInfo(InOwnerActor, InAvatarActor);
InitializeAbilitySets(InOwnerActor, InAvatarActor);
if (AttributeSetInitializeGroupName.IsValid())
{
InitializeAttributes(AttributeSetInitializeGroupName, AttributeSetInitializeLevel, true);
}
bAbilitySystemInitialized = true;
OnAbilitySystemInitialized.Broadcast();
}
}
void UGGA_AbilitySystemComponent::UninitializeAbilitySystem()
{
if (bAbilitySystemInitialized)
{
bAbilitySystemInitialized = false;
OnAbilitySystemUninitialized.Broadcast();
}
}
void UGGA_AbilitySystemComponent::InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor)
{
FGameplayAbilityActorInfo* ActorInfo = AbilityActorInfo.Get();
check(ActorInfo);
check(InOwnerActor);
const bool AvatarChanged = InAvatarActor && (InAvatarActor != ActorInfo->AvatarActor);
Super::InitAbilityActorInfo(InOwnerActor, InAvatarActor);
if (GetWorld() && !GetWorld()->IsGameWorld())
{
return;
}
if (AvatarChanged)
{
RegisterToGlobalAbilitySystem();
ABILITYLIST_SCOPE_LOCK();
for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items)
{
if (IGGA_GameplayAbilityInterface* AbilityCDO = Cast<IGGA_GameplayAbilityInterface>(AbilitySpec.Ability))
{
AbilityCDO->TryActivateAbilityOnSpawn(AbilityActorInfo.Get(), AbilitySpec);
}
}
}
}
void UGGA_AbilitySystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UnregisterToGlobalAbilitySystem();
Super::EndPlay(EndPlayReason);
}
void UGGA_AbilitySystemComponent::InitializeComponent()
{
SetReplicationMode(AbilitySystemReplicationMode);
Super::InitializeComponent();
}
void UGGA_AbilitySystemComponent::InitializeAbilitySets(AActor* InOwnerActor, AActor* InAvatarActor)
{
if (GetNetMode() != NM_Client)
{
for (int32 i = DefaultAbilitySet_GrantedHandles.Num() - 1; i >= 0; i--)
{
DefaultAbilitySet_GrantedHandles[i].TakeFromAbilitySystem(this);
}
DefaultAbilitySet_GrantedHandles.Empty();
for (TObjectPtr<const UGGA_AbilitySet> AbilitySet : DefaultAbilitySets)
{
if (!AbilitySet)
continue;
AbilitySet->GiveToAbilitySystem(this, /*inout*/ &DefaultAbilitySet_GrantedHandles.AddDefaulted_GetRef(), this);
}
}
}
void UGGA_AbilitySystemComponent::InitializeAttributes(FGGA_AttributeGroupName GroupName, int32 Level, bool bInitialInit)
{
if (const UGGA_AbilitySystemGlobals* Globals = Cast<UGGA_AbilitySystemGlobals>(UGGA_AbilitySystemGlobals::GetAbilitySystemGlobals()))
{
Globals->InitAttributeSetDefaults(this, GroupName, Level, bInitialInit);
}
else
{
UE_LOG(LogGGA_AbilitySystem, Warning, TEXT("Failed to InitializeAttributes as your project is not configured to use GGA_AbilitySystemGlobals(or derived class)."));
}
}
void UGGA_AbilitySystemComponent::SendGameplayEventToActor_Replicated(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload)
{
if (IsValid(Actor) && EventTag.IsValid())
{
if (Actor->HasAuthority())
{
MulticastSendGameplayEventToActor(Actor, EventTag, Payload);
}
else
{
ServerSendGameplayEventToActor(Actor, EventTag, Payload);
}
}
}
void UGGA_AbilitySystemComponent::ServerSendGameplayEventToActor_Implementation(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload)
{
MulticastSendGameplayEventToActor(Actor, EventTag, Payload);
}
bool UGGA_AbilitySystemComponent::ServerSendGameplayEventToActor_Validate(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload)
{
return true;
}
void UGGA_AbilitySystemComponent::MulticastSendGameplayEventToActor_Implementation(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload)
{
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(Actor, EventTag, Payload);
}
void UGGA_AbilitySystemComponent::PostInitProperties()
{
Super::PostInitProperties();
ReplicationMode = AbilitySystemReplicationMode;
}
void UGGA_AbilitySystemComponent::RegisterToGlobalAbilitySystem()
{
if (bRegisteredToGlobalAbilitySystem)
return;
// Register with the global system once we actually have a pawn avatar. We wait until this time since some globally-applied effects may require an avatar.
if (UGGA_GlobalAbilitySystem* GlobalAbilitySystem = UWorld::GetSubsystem<UGGA_GlobalAbilitySystem>(GetWorld()))
{
GlobalAbilitySystem->RegisterASC(this);
bRegisteredToGlobalAbilitySystem = true;
}
}
void UGGA_AbilitySystemComponent::UnregisterToGlobalAbilitySystem()
{
if (!bRegisteredToGlobalAbilitySystem)
return;
if (UGGA_GlobalAbilitySystem* GlobalAbilitySystem = UWorld::GetSubsystem<UGGA_GlobalAbilitySystem>(GetWorld()))
{
GlobalAbilitySystem->UnregisterASC(this);
bRegisteredToGlobalAbilitySystem = false;
}
}
#pragma endregion
#pragma region AbilitiesActivation
bool UGGA_AbilitySystemComponent::IsActivationGroupBlocked(EGGA_AbilityActivationGroup Group) const
{
bool bBlocked = false;
switch (Group)
{
case EGGA_AbilityActivationGroup::Independent:
// Independent abilities are never blocked.
bBlocked = false;
break;
case EGGA_AbilityActivationGroup::Exclusive_Replaceable:
case EGGA_AbilityActivationGroup::Exclusive_Blocking:
// Exclusive abilities can activate if nothing is blocking.
bBlocked = (ActivationGroupCounts[(uint8)EGGA_AbilityActivationGroup::Exclusive_Blocking] > 0);
break;
default:
checkf(false, TEXT("IsActivationGroupBlocked: Invalid ActivationGroup [%d]\n"), (uint8)Group);
break;
}
return bBlocked;
}
void UGGA_AbilitySystemComponent::AddAbilityToActivationGroup(EGGA_AbilityActivationGroup Group, UGameplayAbility* Ability)
{
check(Ability);
check(ActivationGroupCounts[(uint8)Group] < INT32_MAX);
ActivationGroupCounts[(uint8)Group]++;
const bool bReplicateCancelAbility = false;
switch (Group)
{
case EGGA_AbilityActivationGroup::Independent:
// Independent abilities do not cancel any other abilities.
break;
case EGGA_AbilityActivationGroup::Exclusive_Replaceable:
case EGGA_AbilityActivationGroup::Exclusive_Blocking:
CancelActivationGroupAbilities(EGGA_AbilityActivationGroup::Exclusive_Replaceable, Ability, bReplicateCancelAbility);
break;
default:
checkf(false, TEXT("AddAbilityToActivationGroup: In valid ActivationGroup [%d]\n"), (uint8)Group);
break;
}
const int32 ExclusiveCount = ActivationGroupCounts[(uint8)EGGA_AbilityActivationGroup::Exclusive_Replaceable] + ActivationGroupCounts[(uint8)EGGA_AbilityActivationGroup::Exclusive_Blocking];
if (!ensure(ExclusiveCount <= 1))
{
UE_LOG(LogGGA_AbilitySystem, Error, TEXT("AddAbilityToActivationGroup: Multiple exclusive abilities are running."));
}
}
void UGGA_AbilitySystemComponent::RemoveAbilityFromActivationGroup(EGGA_AbilityActivationGroup Group, UGameplayAbility* Ability)
{
check(Ability);
check(ActivationGroupCounts[(uint8)Group] > 0);
ActivationGroupCounts[(uint8)Group]--;
}
bool UGGA_AbilitySystemComponent::CanChangeActivationGroup(EGGA_AbilityActivationGroup NewGroup, UGameplayAbility* Ability) const
{
if (Ability == nullptr || !Ability->IsInstantiated() || !Ability->IsActive())
{
return false;
}
IGGA_GameplayAbilityInterface* AbilityInterface = Cast<IGGA_GameplayAbilityInterface>(Ability);
if (AbilityInterface == nullptr)
{
return false;
}
if (AbilityInterface->GetActivationGroup() == NewGroup)
{
return true;
}
if ((AbilityInterface->GetActivationGroup() != EGGA_AbilityActivationGroup::Exclusive_Blocking) && IsActivationGroupBlocked(NewGroup))
{
// This ability can't change groups if it's blocked (unless it is the one doing the blocking).
return false;
}
if ((NewGroup == EGGA_AbilityActivationGroup::Exclusive_Replaceable) && !Ability->CanBeCanceled())
{
// This ability can't become replaceable if it can't be canceled.
return false;
}
return true;
}
bool UGGA_AbilitySystemComponent::ChangeActivationGroup(EGGA_AbilityActivationGroup NewGroup, UGameplayAbility* Ability)
{
if (!CanChangeActivationGroup(NewGroup, Ability))
{
return false;
}
IGGA_GameplayAbilityInterface* AbilityInterface = Cast<IGGA_GameplayAbilityInterface>(Ability);
if (AbilityInterface == nullptr)
{
return false;
}
if (AbilityInterface->GetActivationGroup() != NewGroup)
{
RemoveAbilityFromActivationGroup(AbilityInterface->GetActivationGroup(), Ability);
AddAbilityToActivationGroup(NewGroup, Ability);
AbilityInterface->SetActivationGroup(NewGroup);
}
return true;
}
void UGGA_AbilitySystemComponent::NotifyAbilityActivated(const FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability)
{
Super::NotifyAbilityActivated(Handle, Ability);
if (IGGA_GameplayAbilityInterface* AbilityInterface = Cast<IGGA_GameplayAbilityInterface>(Ability))
{
AddAbilityToActivationGroup(AbilityInterface->GetActivationGroup(), Ability);
}
OnAbilityActivated.Broadcast(Handle, Ability);
}
void UGGA_AbilitySystemComponent::NotifyAbilityFailed(const FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason)
{
Super::NotifyAbilityFailed(Handle, Ability, FailureReason);
if (APawn* Avatar = Cast<APawn>(GetAvatarActor()))
{
if (!Avatar->IsLocallyControlled() && Ability->IsSupportedForNetworking())
{
ClientNotifyAbilityActivationFailed(Ability, FailureReason);
return;
}
}
HandleAbilityActivationFailed(Ability, FailureReason);
}
void UGGA_AbilitySystemComponent::ClientNotifyAbilityActivationFailed_Implementation(const UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason)
{
HandleAbilityActivationFailed(Ability, FailureReason);
}
void UGGA_AbilitySystemComponent::HandleAbilityActivationFailed(const UGameplayAbility* Ability, const FGameplayTagContainer& FailureReason)
{
OnAbilityActivationFailed.Broadcast(Ability, FailureReason);
if (const IGGA_GameplayAbilityInterface* AbilityInterface = Cast<const IGGA_GameplayAbilityInterface>(Ability))
{
AbilityInterface->HandleActivationFailed(FailureReason);
}
}
#pragma endregion
#pragma region AbilityCancellation
void UGGA_AbilitySystemComponent::CancelAbilitiesByFunc(TShouldCancelAbilityFunc ShouldCancelFunc, bool bReplicateCancelAbility)
{
ABILITYLIST_SCOPE_LOCK();
for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items)
{
if (AbilitySpec.Ability == nullptr || !AbilitySpec.IsActive())
{
continue;
}
#if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION <5
if (AbilitySpec.Ability->GetInstancingPolicy() != EGameplayAbilityInstancingPolicy::NonInstanced)
#endif
{
// Cancel all the spawned instances, not the CDO.
TArray<UGameplayAbility*> Instances = AbilitySpec.GetAbilityInstances();
for (UGameplayAbility* AbilityInstance : Instances)
{
if (ShouldCancelFunc(AbilityInstance, AbilitySpec.Handle))
{
if (AbilityInstance->CanBeCanceled())
{
AbilityInstance->CancelAbility(AbilitySpec.Handle, AbilityActorInfo.Get(), AbilityInstance->GetCurrentActivationInfo(), bReplicateCancelAbility);
}
else
{
UE_LOG(LogGGA_AbilitySystem, Error, TEXT("CancelAbilitiesByFunc: Can't cancel ability [%s] because CanBeCanceled is false."), *AbilityInstance->GetName());
}
}
}
}
#if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION <5
else
{
// Cancel the non-instanced ability CDO.
if (ShouldCancelFunc(AbilitySpec.Ability, AbilitySpec.Handle))
{
// Non-instanced abilities can always be canceled.
check(AbilitySpec.Ability->CanBeCanceled());
AbilitySpec.Ability->CancelAbility(AbilitySpec.Handle, AbilityActorInfo.Get(), FGameplayAbilityActivationInfo(), bReplicateCancelAbility);
}
}
#endif
}
}
void UGGA_AbilitySystemComponent::CancelActivationGroupAbilities(EGGA_AbilityActivationGroup Group, UGameplayAbility* IgnoreAbility, bool bReplicateCancelAbility)
{
auto ShouldCancelFunc = [this, Group, IgnoreAbility](const UGameplayAbility* Ability, FGameplayAbilitySpecHandle Handle)
{
bool SameGroup = false;
if (const IGGA_GameplayAbilityInterface* AbilityInterface = Cast<IGGA_GameplayAbilityInterface>(Ability))
{
SameGroup = AbilityInterface->GetActivationGroup() == Group;
}
return (SameGroup && (Ability != IgnoreAbility));
};
CancelAbilitiesByFunc(ShouldCancelFunc, bReplicateCancelAbility);
}
void UGGA_AbilitySystemComponent::NotifyAbilityEnded(FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability, bool bWasCancelled)
{
Super::NotifyAbilityEnded(Handle, Ability, bWasCancelled);
if (const IGGA_GameplayAbilityInterface* AbilityInterface = Cast<IGGA_GameplayAbilityInterface>(Ability))
{
RemoveAbilityFromActivationGroup(AbilityInterface->GetActivationGroup(), Ability);
}
AbilityEndedEvent.Broadcast(Handle, Ability, bWasCancelled);
}
void UGGA_AbilitySystemComponent::HandleChangeAbilityCanBeCanceled(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bCanBeCanceled)
{
Super::HandleChangeAbilityCanBeCanceled(AbilityTags, RequestingAbility, bCanBeCanceled);
//@TODO: Apply any special logic like blocking input or movement
}
#pragma endregion
#pragma region Abilities
bool UGGA_AbilitySystemComponent::GetCooldownRemainingForTags(FGameplayTagContainer CooldownTags, float& TimeRemaining, float& CooldownDuration)
{
if (CooldownTags.Num() > 0)
{
TimeRemaining = 0.f;
CooldownDuration = 0.f;
FGameplayEffectQuery const Query = FGameplayEffectQuery::MakeQuery_MatchAnyOwningTags(CooldownTags);
TArray<TPair<float, float>> DurationAndTimeRemaining = GetActiveEffectsTimeRemainingAndDuration(Query);
if (DurationAndTimeRemaining.Num() > 0)
{
int32 BestIdx = 0;
float LongestTime = DurationAndTimeRemaining[0].Key;
for (int32 Idx = 1; Idx < DurationAndTimeRemaining.Num(); ++Idx)
{
if (DurationAndTimeRemaining[Idx].Key > LongestTime)
{
LongestTime = DurationAndTimeRemaining[Idx].Key;
BestIdx = Idx;
}
}
TimeRemaining = DurationAndTimeRemaining[BestIdx].Key;
CooldownDuration = DurationAndTimeRemaining[BestIdx].Value;
return true;
}
}
return false;
}
bool UGGA_AbilitySystemComponent::BatchRPCTryActivateAbility(FGameplayAbilitySpecHandle InAbilityHandle,
bool EndAbilityImmediately)
{
bool AbilityActivated = false;
if (InAbilityHandle.IsValid())
{
FScopedServerAbilityRPCBatcher AbilityRpcBatching(this, InAbilityHandle);
AbilityActivated = TryActivateAbility(InAbilityHandle, true);
if (EndAbilityImmediately)
{
FGameplayAbilitySpec* AbilitySpec = FindAbilitySpecFromHandle(InAbilityHandle);
if (AbilitySpec)
{
if (IGGA_GameplayAbilityInterface* AbilityInterface = Cast<IGGA_GameplayAbilityInterface>(AbilitySpec->GetPrimaryInstance()))
{
AbilityInterface->ExternalEndAbility();
}
}
}
return AbilityActivated;
}
return AbilityActivated;
}
#pragma endregion
#pragma region GameplayTags
void UGGA_AbilitySystemComponent::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const
{
TagContainer.Reset(); // Fix for Version under 5.2
TagContainer.AppendTags(GameplayTagCountContainer.GetExplicitGameplayTags());
}
FString UGGA_AbilitySystemComponent::GetOwnedGameplayTagsString()
{
FString BlockedTagsStrings;
for (auto Tag : GameplayTagCountContainer.GetExplicitGameplayTags())
{
BlockedTagsStrings.Append(FString::Printf(TEXT("%s (%d),\n"), *Tag.ToString(), GameplayTagCountContainer.GetTagCount(Tag)));
}
return BlockedTagsStrings;
}
void UGGA_AbilitySystemComponent::GetAdditionalActivationTagRequirements(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer& OutActivationRequired,
FGameplayTagContainer& OutActivationBlocked) const
{
if (TagRelationshipMapping)
{
FGameplayTagContainer ActorTags;
GetOwnedGameplayTags(ActorTags);
TagRelationshipMapping->GetRequiredAndBlockedActivationTagsV2(ActorTags, AbilityTags, &OutActivationRequired, &OutActivationBlocked);
// TagRelationshipMapping->GetRequiredAndBlockedActivationTags(AbilityTags, &OutActivationRequired, &OutActivationBlocked);
}
}
void UGGA_AbilitySystemComponent::ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bEnableBlockTags,
const FGameplayTagContainer& BlockTags, bool bExecuteCancelTags, const FGameplayTagContainer& CancelTags)
{
FGameplayTagContainer ModifiedBlockTags = BlockTags;
FGameplayTagContainer ModifiedCancelTags = CancelTags;
if (TagRelationshipMapping)
{
FGameplayTagContainer ActorTags;
GetOwnedGameplayTags(ActorTags);
// Use the mapping to expand the ability tags into block and cancel tag
TagRelationshipMapping->GetAbilityTagsToBlockAndCancelV2(ActorTags, AbilityTags, &ModifiedBlockTags, &ModifiedCancelTags);
// TagRelationshipMapping->GetAbilityTagsToBlockAndCancel(AbilityTags, &ModifiedBlockTags, &ModifiedCancelTags);
}
Super::ApplyAbilityBlockAndCancelTags(AbilityTags, RequestingAbility, bEnableBlockTags, ModifiedBlockTags, bExecuteCancelTags, ModifiedCancelTags);
//@TODO: Apply any special logic like blocking input or movement
}
void UGGA_AbilitySystemComponent::SetTagRelationshipMapping(UGGA_AbilityTagRelationshipMapping* NewMapping)
{
TagRelationshipMapping = NewMapping;
}
#pragma endregion
#pragma region Attributes
FString UGGA_AbilitySystemComponent::GetOwnedGameplayAttributeSetString()
{
FString AttributeSetString;
TArray<FGameplayAttribute> Attributes;
GetAllAttributes(Attributes);
for (const auto& Attribute : Attributes)
{
AttributeSetString.Append(
FString::Printf(TEXT("%s : %.2f \n"), *Attribute.GetName(), GetNumericAttribute(Attribute)));
}
return AttributeSetString;
}
#pragma endregion
#pragma region TargetData
void UGGA_AbilitySystemComponent::GetAbilityTargetData(const FGameplayAbilitySpecHandle AbilityHandle, FGameplayAbilityActivationInfo ActivationInfo,
FGameplayAbilityTargetDataHandle& OutTargetDataHandle)
{
TSharedPtr<FAbilityReplicatedDataCache> ReplicatedData = AbilityTargetDataMap.Find(FGameplayAbilitySpecHandleAndPredictionKey(AbilityHandle, ActivationInfo.GetActivationPredictionKey()));
if (ReplicatedData.IsValid())
{
OutTargetDataHandle = ReplicatedData->TargetData;
}
}
#pragma endregion

View File

@@ -0,0 +1,31 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GGA_AbilitySystemStructLibrary.h"
bool FGGA_GameplayEffectContainerSpec::HasValidEffects() const
{
return TargetGameplayEffectSpecs.Num() > 0;
}
bool FGGA_GameplayEffectContainerSpec::HasValidTargets() const
{
return TargetData.Num() > 0;
}
void FGGA_GameplayEffectContainerSpec::AddTargets(const TArray<FHitResult>& HitResults, const TArray<AActor*>& TargetActors)
{
for (const FHitResult& HitResult : HitResults)
{
FGameplayAbilityTargetData_SingleTargetHit* NewData = new FGameplayAbilityTargetData_SingleTargetHit(HitResult);
TargetData.Add(NewData);
}
if (TargetActors.Num() > 0)
{
FGameplayAbilityTargetData_ActorArray* NewData = new FGameplayAbilityTargetData_ActorArray();
NewData->TargetActorArray.Append(TargetActors);
TargetData.Add(NewData);
}
}

View File

@@ -0,0 +1,180 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GGA_AbilityTagRelationshipMapping.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GGA_AbilityTagRelationshipMapping)
void UGGA_AbilityTagRelationshipMapping::GetAbilityTagsToBlockAndCancelV2(const FGameplayTagContainer& ActorTags, const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutTagsToBlock,
FGameplayTagContainer* OutTagsToCancel) const
{
TArray<FGameplayTag> AbilitiesWithLayeredRule;
for (int32 i = 0; i < Layered.Num(); i++)
{
if (!ActorTags.IsEmpty() && Layered[i].ActorTagQuery.Matches(ActorTags))
{
const TArray<FGGA_AbilityTagRelationship>& LayeredAbilityTagRelationships = Layered[i].AbilityTagRelationships;
// Simple iteration for now
for (int32 j = 0; j < LayeredAbilityTagRelationships.Num(); j++)
{
const FGGA_AbilityTagRelationship& Tags = LayeredAbilityTagRelationships[j];
if (AbilityTags.HasTag(Tags.AbilityTag))
{
if (OutTagsToBlock)
{
OutTagsToBlock->AppendTags(Tags.AbilityTagsToBlock);
}
if (OutTagsToCancel)
{
OutTagsToCancel->AppendTags(Tags.AbilityTagsToCancel);
}
AbilitiesWithLayeredRule.Add(Tags.AbilityTag);
}
}
}
}
// Simple iteration for now
for (int32 i = 0; i < AbilityTagRelationships.Num(); i++)
{
const FGGA_AbilityTagRelationship& Tags = AbilityTagRelationships[i];
if (AbilityTags.HasTag(Tags.AbilityTag) && !AbilitiesWithLayeredRule.Contains(Tags.AbilityTag))
{
if (OutTagsToBlock)
{
OutTagsToBlock->AppendTags(Tags.AbilityTagsToBlock);
}
if (OutTagsToCancel)
{
OutTagsToCancel->AppendTags(Tags.AbilityTagsToCancel);
}
}
}
}
void UGGA_AbilityTagRelationshipMapping::GetAbilityTagsToBlockAndCancel(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutTagsToBlock, FGameplayTagContainer* OutTagsToCancel) const
{
// Simple iteration for now
for (int32 i = 0; i < AbilityTagRelationships.Num(); i++)
{
const FGGA_AbilityTagRelationship& Tags = AbilityTagRelationships[i];
if (AbilityTags.HasTag(Tags.AbilityTag))
{
if (OutTagsToBlock)
{
OutTagsToBlock->AppendTags(Tags.AbilityTagsToBlock);
}
if (OutTagsToCancel)
{
OutTagsToCancel->AppendTags(Tags.AbilityTagsToCancel);
}
}
}
}
void UGGA_AbilityTagRelationshipMapping::GetRequiredAndBlockedActivationTagsV2(const FGameplayTagContainer& ActorTags, const FGameplayTagContainer& AbilityTags,
FGameplayTagContainer* OutActivationRequired, FGameplayTagContainer* OutActivationBlocked) const
{
TArray<FGameplayTag> AbilitiesWithLayeredRule;
for (int32 i = 0; i < Layered.Num(); i++)
{
if (!ActorTags.IsEmpty() && Layered[i].ActorTagQuery.Matches(ActorTags))
{
const TArray<FGGA_AbilityTagRelationship>& LayeredAbilityTagRelationships = Layered[i].AbilityTagRelationships;
// Simple iteration for now
for (int32 j = 0; j < LayeredAbilityTagRelationships.Num(); j++)
{
const FGGA_AbilityTagRelationship& Tags = LayeredAbilityTagRelationships[j];
if (AbilityTags.HasTag(Tags.AbilityTag))
{
if (OutActivationRequired)
{
OutActivationRequired->AppendTags(Tags.ActivationRequiredTags);
}
if (OutActivationBlocked)
{
OutActivationBlocked->AppendTags(Tags.ActivationBlockedTags);
}
AbilitiesWithLayeredRule.Add(Tags.AbilityTag);
}
}
}
}
// Simple iteration for now
for (int32 i = 0; i < AbilityTagRelationships.Num(); i++)
{
const FGGA_AbilityTagRelationship& Tags = AbilityTagRelationships[i];
if (AbilityTags.HasTag(Tags.AbilityTag) && !AbilitiesWithLayeredRule.Contains(Tags.AbilityTag))
{
if (OutActivationRequired)
{
OutActivationRequired->AppendTags(Tags.ActivationRequiredTags);
}
if (OutActivationBlocked)
{
OutActivationBlocked->AppendTags(Tags.ActivationBlockedTags);
}
}
}
}
void UGGA_AbilityTagRelationshipMapping::GetRequiredAndBlockedActivationTags(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutActivationRequired,
FGameplayTagContainer* OutActivationBlocked) const
{
// Simple iteration for now
for (int32 i = 0; i < AbilityTagRelationships.Num(); i++)
{
const FGGA_AbilityTagRelationship& Tags = AbilityTagRelationships[i];
if (AbilityTags.HasTag(Tags.AbilityTag))
{
if (OutActivationRequired)
{
OutActivationRequired->AppendTags(Tags.ActivationRequiredTags);
}
if (OutActivationBlocked)
{
OutActivationBlocked->AppendTags(Tags.ActivationBlockedTags);
}
}
}
}
bool UGGA_AbilityTagRelationshipMapping::IsAbilityCancelledByTag(const FGameplayTagContainer& AbilityTags, const FGameplayTag& ActionTag) const
{
// Simple iteration for now
for (int32 i = 0; i < AbilityTagRelationships.Num(); i++)
{
const FGGA_AbilityTagRelationship& Tags = AbilityTagRelationships[i];
if (Tags.AbilityTag == ActionTag && Tags.AbilityTagsToCancel.HasAny(AbilityTags))
{
return true;
}
}
return false;
}
#if WITH_EDITOR
#include "UObject/ObjectSaveContext.h"
void UGGA_AbilityTagRelationshipMapping::PreSave(FObjectPreSaveContext SaveContext)
{
for (FGGA_AbilityTagRelationship& Rel : AbilityTagRelationships)
{
Rel.EditorFriendlyName = Rel.DevDescription.IsEmpty() ? Rel.AbilityTag.ToString() : Rel.DevDescription;
}
for (FGGA_AbilityTagRelationshipsWithQuery& RelationShips : Layered)
{
RelationShips.EditorFriendlyName = RelationShips.ActorTagQuery.IsEmpty() ? TEXT("Empty Query") : RelationShips.ActorTagQuery.GetDescription();
for (FGGA_AbilityTagRelationship& AbilityTagRelationship : RelationShips.AbilityTagRelationships)
{
AbilityTagRelationship.EditorFriendlyName = AbilityTagRelationship.DevDescription.IsEmpty() ? AbilityTagRelationship.AbilityTag.ToString() : AbilityTagRelationship.DevDescription;
}
}
Super::PreSave(SaveContext);
}
#endif

View File

@@ -0,0 +1,24 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GGA_GameplayTags.h"
namespace GGA_AbilityActivateFailTags
{
UE_DEFINE_GAMEPLAY_TAG_COMMENT(IsDead, "GGF.Ability.ActivateFail.IsDead", "Ability failed to activate because its owner is dead.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Cooldown, "GGF.Ability.ActivateFail.Cooldown", "Ability failed to activate because it is on cool down.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Cost, "GGF.Ability.ActivateFail.Cost", "Ability failed to activate because it did not pass the cost checks.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(TagsBlocked, "GGF.Ability.ActivateFail.TagsBlocked", "Ability failed to activate because tags are blocking it.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(TagsMissing, "GGF.Ability.ActivateFail.TagsMissing", "Ability failed to activate because tags are missing.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Networking, "GGF.Ability.ActivateFail.Networking", "Ability failed to activate because it did not pass the network checks.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(ActivationGroup, "GGF.Ability.ActivateFail.ActivationGroup", "Ability failed to activate because of its activation group.");
}
namespace GGA_AbilityTraitTags
{
UE_DEFINE_GAMEPLAY_TAG_COMMENT(ActivationOnSpawn, "GGF.Ability.Trait.ActivationOnSpawn", "Abilities with this tag will be activated right after granted.");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Persistent, "GGF.Ability.Trait.Persistent", "Abilities with this tag should be persistent during gameplay.");
}

View File

@@ -0,0 +1,154 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GGA_GlobalAbilitySystem.h"
#include "GGA_AbilitySystemComponent.h"
#include "Abilities/GameplayAbility.h"
void FGGA_GlobalAppliedAbilityList::AddToASC(TSubclassOf<UGameplayAbility> Ability, UGGA_AbilitySystemComponent* ASC)
{
if (FGameplayAbilitySpecHandle* SpecHandle = Handles.Find(ASC))
{
RemoveFromASC(ASC);
}
UGameplayAbility* AbilityCDO = Ability->GetDefaultObject<UGameplayAbility>();
FGameplayAbilitySpec AbilitySpec(AbilityCDO);
const FGameplayAbilitySpecHandle AbilitySpecHandle = ASC->GiveAbility(AbilitySpec);
Handles.Add(ASC, AbilitySpecHandle);
}
void FGGA_GlobalAppliedAbilityList::RemoveFromASC(UGGA_AbilitySystemComponent* ASC)
{
if (FGameplayAbilitySpecHandle* SpecHandle = Handles.Find(ASC))
{
ASC->ClearAbility(*SpecHandle);
Handles.Remove(ASC);
}
}
void FGGA_GlobalAppliedAbilityList::RemoveFromAll()
{
for (auto& KVP : Handles)
{
if (KVP.Key != nullptr)
{
KVP.Key->ClearAbility(KVP.Value);
}
}
Handles.Empty();
}
void FGGA_GlobalAppliedEffectList::AddToASC(TSubclassOf<UGameplayEffect> Effect, UGGA_AbilitySystemComponent* ASC)
{
if (FActiveGameplayEffectHandle* EffectHandle = Handles.Find(ASC))
{
RemoveFromASC(ASC);
}
const UGameplayEffect* GameplayEffectCDO = Effect->GetDefaultObject<UGameplayEffect>();
const FActiveGameplayEffectHandle GameplayEffectHandle = ASC->ApplyGameplayEffectToSelf(GameplayEffectCDO, /*Level=*/ 1, ASC->MakeEffectContext());
Handles.Add(ASC, GameplayEffectHandle);
}
void FGGA_GlobalAppliedEffectList::RemoveFromASC(UGGA_AbilitySystemComponent* ASC)
{
if (FActiveGameplayEffectHandle* EffectHandle = Handles.Find(ASC))
{
ASC->RemoveActiveGameplayEffect(*EffectHandle);
Handles.Remove(ASC);
}
}
void FGGA_GlobalAppliedEffectList::RemoveFromAll()
{
for (auto& KVP : Handles)
{
if (KVP.Key != nullptr)
{
KVP.Key->RemoveActiveGameplayEffect(KVP.Value);
}
}
Handles.Empty();
}
UGGA_GlobalAbilitySystem::UGGA_GlobalAbilitySystem()
{
}
void UGGA_GlobalAbilitySystem::ApplyAbilityToAll(TSubclassOf<UGameplayAbility> Ability)
{
if ((Ability.Get() != nullptr) && (!AppliedAbilities.Contains(Ability)))
{
FGGA_GlobalAppliedAbilityList& Entry = AppliedAbilities.Add(Ability);
for (UGGA_AbilitySystemComponent* ASC : RegisteredASCs)
{
Entry.AddToASC(Ability, ASC);
}
}
}
void UGGA_GlobalAbilitySystem::ApplyEffectToAll(TSubclassOf<UGameplayEffect> Effect)
{
if ((Effect.Get() != nullptr) && (!AppliedEffects.Contains(Effect)))
{
FGGA_GlobalAppliedEffectList& Entry = AppliedEffects.Add(Effect);
for (UGGA_AbilitySystemComponent* ASC : RegisteredASCs)
{
Entry.AddToASC(Effect, ASC);
}
}
}
void UGGA_GlobalAbilitySystem::RemoveAbilityFromAll(TSubclassOf<UGameplayAbility> Ability)
{
if ((Ability.Get() != nullptr) && AppliedAbilities.Contains(Ability))
{
FGGA_GlobalAppliedAbilityList& Entry = AppliedAbilities[Ability];
Entry.RemoveFromAll();
AppliedAbilities.Remove(Ability);
}
}
void UGGA_GlobalAbilitySystem::RemoveEffectFromAll(TSubclassOf<UGameplayEffect> Effect)
{
if ((Effect.Get() != nullptr) && AppliedEffects.Contains(Effect))
{
FGGA_GlobalAppliedEffectList& Entry = AppliedEffects[Effect];
Entry.RemoveFromAll();
AppliedEffects.Remove(Effect);
}
}
void UGGA_GlobalAbilitySystem::RegisterASC(UGGA_AbilitySystemComponent* ASC)
{
check(ASC);
for (auto& Entry : AppliedAbilities)
{
Entry.Value.AddToASC(Entry.Key, ASC);
}
for (auto& Entry : AppliedEffects)
{
Entry.Value.AddToASC(Entry.Key, ASC);
}
RegisteredASCs.AddUnique(ASC);
}
void UGGA_GlobalAbilitySystem::UnregisterASC(UGGA_AbilitySystemComponent* ASC)
{
check(ASC);
for (auto& Entry : AppliedAbilities)
{
Entry.Value.RemoveFromASC(ASC);
}
for (auto& Entry : AppliedEffects)
{
Entry.Value.RemoveFromASC(ASC);
}
RegisteredASCs.Remove(ASC);
}

View File

@@ -0,0 +1,9 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GGA_LogChannels.h"
DEFINE_LOG_CATEGORY(LogGGA_Ability);
DEFINE_LOG_CATEGORY(LogGGA_AbilitySystem);
DEFINE_LOG_CATEGORY(LogGGA_Tasks);

View File

@@ -0,0 +1,66 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GameplayActors/GGA_Character.h"
#include "AbilitySystemComponent.h"
#include "Components/GameFrameworkComponentManager.h"
AGGA_Character::AGGA_Character(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
{
PrimaryActorTick.bCanEverTick = true;
}
void AGGA_Character::PreInitializeComponents()
{
Super::PreInitializeComponents();
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
}
void AGGA_Character::BeginPlay()
{
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
Super::BeginPlay();
}
void AGGA_Character::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
Super::EndPlay(EndPlayReason);
}
void AGGA_Character::OnRep_Controller()
{
Super::OnRep_Controller();
ReceivePlayerController();
}
void AGGA_Character::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
ReceivePlayerState();
}
UAbilitySystemComponent* AGGA_Character::GetAbilitySystemComponent() const
{
if (UAbilitySystemComponent* BpProvidedASC = CustomGetAbilitySystemComponent())
{
return BpProvidedASC;
}
return nullptr;
}
void AGGA_Character::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const
{
}
// Called to bind functionality to input
void AGGA_Character::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}

View File

@@ -0,0 +1,21 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GameplayActors/GGA_CharacterWithAbilities.h"
#include "GGA_AbilitySystemComponent.h"
AGGA_CharacterWithAbilities::AGGA_CharacterWithAbilities(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
{
AbilitySystemComponent = CreateDefaultSubobject<UGGA_AbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
}
UAbilitySystemComponent* AGGA_CharacterWithAbilities::GetAbilitySystemComponent() const
{
if (UAbilitySystemComponent* BpProvidedASC = CustomGetAbilitySystemComponent())
{
return BpProvidedASC;
}
return AbilitySystemComponent;
}

View File

@@ -0,0 +1,116 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GameplayActors/GGA_GameState.h"
#include "GGA_AbilitySystemComponent.h"
#include "Components/GameFrameworkComponentManager.h"
#include "Components/GameStateComponent.h"
#include "Containers/Array.h"
// #include UE_INLINE_GENERATED_CPP_BY_NAME(ModularGameState)
AGGA_GameStateBase::AGGA_GameStateBase(const FObjectInitializer& ObjectInitializer)
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = true;
AbilitySystemComponent = ObjectInitializer.CreateDefaultSubobject<UGGA_AbilitySystemComponent>(this, TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
}
void AGGA_GameStateBase::PostInitializeComponents()
{
Super::PostInitializeComponents();
if (AbilitySystemComponent)
{
AbilitySystemComponent->InitAbilityActorInfo(/*Owner=*/ this, /*Avatar=*/ this);
}
}
UAbilitySystemComponent* AGGA_GameStateBase::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
void AGGA_GameStateBase::PreInitializeComponents()
{
Super::PreInitializeComponents();
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
}
void AGGA_GameStateBase::BeginPlay()
{
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
Super::BeginPlay();
}
void AGGA_GameStateBase::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
Super::EndPlay(EndPlayReason);
}
AGGA_GameState::AGGA_GameState(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = true;
AbilitySystemComponent = ObjectInitializer.CreateDefaultSubobject<UGGA_AbilitySystemComponent>(this, TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
}
UAbilitySystemComponent* AGGA_GameState::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
void AGGA_GameState::PreInitializeComponents()
{
Super::PreInitializeComponents();
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
}
void AGGA_GameState::PostInitializeComponents()
{
Super::PostInitializeComponents();
if (AbilitySystemComponent)
{
AbilitySystemComponent->InitAbilityActorInfo(/*Owner=*/ this, /*Avatar=*/ this);
}
}
void AGGA_GameState::BeginPlay()
{
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
Super::BeginPlay();
}
void AGGA_GameState::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
Super::EndPlay(EndPlayReason);
}
void AGGA_GameState::HandleMatchHasStarted()
{
Super::HandleMatchHasStarted();
TArray<UGameStateComponent*> ModularComponents;
GetComponents(ModularComponents);
for (UGameStateComponent* Component : ModularComponents)
{
Component->HandleMatchHasStarted();
}
}

View File

@@ -0,0 +1,72 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GameplayActors/GGA_PlayerState.h"
#include "Components/GameFrameworkComponentManager.h"
#include "Components/PlayerStateComponent.h"
AGGA_PlayerState::AGGA_PlayerState(const FObjectInitializer& ObjectInitializer):Super(ObjectInitializer)
{
AbilitySystemComponent = ObjectInitializer.CreateDefaultSubobject<UGGA_AbilitySystemComponent>(this, TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
}
void AGGA_PlayerState::PreInitializeComponents()
{
Super::PreInitializeComponents();
UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this);
}
void AGGA_PlayerState::BeginPlay()
{
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady);
Super::BeginPlay();
}
void AGGA_PlayerState::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this);
Super::EndPlay(EndPlayReason);
}
void AGGA_PlayerState::Reset()
{
TArray<UPlayerStateComponent*> ModularComponents;
GetComponents(ModularComponents);
for (UPlayerStateComponent* Component : ModularComponents)
{
Component->Reset();
}
Super::Reset();
}
void AGGA_PlayerState::ClientInitialize(AController* C)
{
Super::ClientInitialize(C);
ReceiveClientInitialize(C);
}
UAbilitySystemComponent* AGGA_PlayerState::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
void AGGA_PlayerState::CopyProperties(APlayerState* PlayerState)
{
Super::CopyProperties(PlayerState);
TInlineComponentArray<UPlayerStateComponent*> PlayerStateComponents;
GetComponents(PlayerStateComponents);
for (UPlayerStateComponent* SourcePSComp : PlayerStateComponents)
{
if (UPlayerStateComponent* TargetComp = Cast<UPlayerStateComponent>(static_cast<UObject*>(FindObjectWithOuter(PlayerState, SourcePSComp->GetClass(), SourcePSComp->GetFName()))))
{
SourcePSComp->CopyProperties(TargetComp);
}
}
}

View File

@@ -0,0 +1,25 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GenericGameplayAbilities.h"
#include "Misc/Paths.h"
#include "GameplayTagsManager.h"
#define LOCTEXT_NAMESPACE "FGGameplayAbilitiesModule"
void FGenericGameplayAbilitiesModule::StartupModule()
{
static FString PluginName = TEXT("GenericGameplayAbilities");
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
UGameplayTagsManager::Get().AddTagIniSearchPath(FPaths::ProjectPluginsDir() / PluginName / TEXT("Config/Tags"));
}
void FGenericGameplayAbilitiesModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FGenericGameplayAbilitiesModule, GenericGameplayAbilities)

View File

@@ -0,0 +1,128 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GGA_AbilitySystemGlobals.h"
#include "GameplayEffect.h"
#include "GGA_GameplayEffectContext.h"
#include "GGA_LogChannels.h"
void UGGA_AbilitySystemGlobals::GlobalPreGameplayEffectSpecApply(FGameplayEffectSpec& Spec, UAbilitySystemComponent* AbilitySystemComponent)
{
for (TScriptInterface Receiver : Receivers)
{
Receiver->ReceiveGlobalPreGameplayEffectSpecApply(Spec, AbilitySystemComponent);
}
}
FGameplayEffectContext* UGGA_AbilitySystemGlobals::AllocGameplayEffectContext() const
{
return new FGGA_GameplayEffectContext();
}
const UAbilitySystemGlobals* UGGA_AbilitySystemGlobals::GetAbilitySystemGlobals()
{
if (const UAbilitySystemGlobals* ASG = IGameplayAbilitiesModule::Get().GetAbilitySystemGlobals())
{
return ASG;
}
return nullptr;
}
const UAbilitySystemGlobals* UGGA_AbilitySystemGlobals::GetTypedAbilitySystemGloabls(TSubclassOf<UAbilitySystemGlobals> DesiredClass)
{
if (UClass* RealClass = DesiredClass)
{
if (const UAbilitySystemGlobals* ASG = IGameplayAbilitiesModule::Get().GetAbilitySystemGlobals())
{
if (ASG->GetClass()->IsChildOf(RealClass))
{
return ASG;
}
}
}
return nullptr;
}
void UGGA_AbilitySystemGlobals::RegisterEventReceiver(TScriptInterface<IGGA_AbilitySystemGlobalsEventReceiver> NewReceiver)
{
UGGA_AbilitySystemGlobals* Globals = dynamic_cast<UGGA_AbilitySystemGlobals*>(&Get());
if (Globals != nullptr)
{
if (NewReceiver != nullptr && IsValid(NewReceiver.GetObject()) && !Globals->Receivers.Contains(NewReceiver))
{
Globals->Receivers.Add(NewReceiver);
UE_LOG(LogGGA_AbilitySystem, VeryVerbose, TEXT("RegisterEventReceiver:%s"), *NewReceiver.GetObject()->GetName());
}
}
}
void UGGA_AbilitySystemGlobals::UnregisterEventReceiver(TScriptInterface<IGGA_AbilitySystemGlobalsEventReceiver> NewReceiver)
{
UGGA_AbilitySystemGlobals* Globals = dynamic_cast<UGGA_AbilitySystemGlobals*>(&Get());
if (Globals != nullptr)
{
if (NewReceiver != nullptr && IsValid(NewReceiver.GetObject()) && Globals->Receivers.Contains(NewReceiver))
{
Globals->Receivers.Remove(NewReceiver);
UE_LOG(LogGGA_AbilitySystem, VeryVerbose, TEXT("UnregisterEventReceiver:%s"), *NewReceiver.GetObject()->GetName());
}
}
}
TArray<UCurveTable*> UGGA_AbilitySystemGlobals::GetAttributeDefaultsTables() const
{
return GlobalAttributeDefaultsTables;
}
void UGGA_AbilitySystemGlobals::InitAttributeSetDefaults(UAbilitySystemComponent* AbilitySystem, const FGGA_AttributeGroupName& GroupName, int32 Level, bool bInitialInit) const
{
if (GlobalAttributeSetInitter.IsValid())
{
if (FAttributeSetInitter* Initter = GetAttributeSetInitter())
{
if (GroupName.IsValid())
{
Initter->InitAttributeSetDefaults(AbilitySystem, GroupName.GetName(), Level, bInitialInit);
}
}
}
else
{
UE_LOG(LogGGA_AbilitySystem, Warning, TEXT("You don't have any GlobalAttributeSetDefaultsTableNames configured in your AbilitySystemGlobals setting."))
}
}
void UGGA_AbilitySystemGlobals::ApplyAttributeDefault(UAbilitySystemComponent* AbilitySystem, FGameplayAttribute& InAttribute, const FGGA_AttributeGroupName& GroupName, int32 Level) const
{
if (GlobalAttributeSetInitter.IsValid())
{
if (FAttributeSetInitter* Initter = GetAttributeSetInitter())
{
if (GroupName.IsValid())
{
Initter->ApplyAttributeDefault(AbilitySystem, InAttribute, GroupName.GetName(), Level);
}
}
}
else
{
UE_LOG(LogGGA_AbilitySystem, Warning, TEXT("You don't have any GlobalAttributeSetDefaultsTableNames configured in your AbilitySystemGlobals setting."))
}
}
void IGGA_AbilitySystemGlobalsEventReceiver::ReceiveGlobalPreGameplayEffectSpecApply(FGameplayEffectSpec& Spec, UAbilitySystemComponent* AbilitySystemComponent)
{
OnGlobalPreGameplayEffectSpecApply(Spec, AbilitySystemComponent);
FGameplayTagContainer DynamicTags;
Execute_OnGlobalPreGameplayEffectSpecApply_Bp(_getUObject(), Spec, AbilitySystemComponent, DynamicTags);
if (!DynamicTags.IsEmpty())
{
Spec.AppendDynamicAssetTags(DynamicTags);
}
}

View File

@@ -0,0 +1,4 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GGA_GameplayAbilityTargetData_Payload.h"

View File

@@ -0,0 +1,143 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Globals/GGA_GameplayEffectContext.h"
FGameplayEffectContext* FGGA_GameplayEffectContext::Duplicate() const
{
FGGA_GameplayEffectContext* NewContext = new FGGA_GameplayEffectContext();
*NewContext = *this;
if (GetHitResult())
{
// Does a deep copy of the hit result
NewContext->AddHitResult(*GetHitResult(), true);
}
return NewContext;
}
UScriptStruct* FGGA_GameplayEffectContext::GetScriptStruct() const
{
return StaticStruct();
}
//Reference this:https://www.thegames.dev/?p=62
bool FGGA_GameplayEffectContext::NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess)
{
bool bCombinedSuccess = FGameplayEffectContext::NetSerialize(Ar, Map, bOutSuccess);
enum RepFlag
{
REP_ContextPayloads,
REP_MAX
};
uint16 RepBits = 0;
if (Ar.IsSaving())
{
if (Payloads.Num() > 0)
{
RepBits |= 1 << REP_ContextPayloads;
}
}
Ar.SerializeBits(&RepBits, REP_MAX);
if (RepBits & (1 << REP_ContextPayloads))
{
// or also check if Ar.IsSaving || Ar.IsLoading
bCombinedSuccess &= SafeNetSerializeTArray_WithNetSerialize<31>(Ar, Payloads, Map);
}
return bCombinedSuccess;
}
TArray<FInstancedStruct>& FGGA_GameplayEffectContext::GetPayloads()
{
return Payloads;
}
void FGGA_GameplayEffectContext::AddOrOverwriteData(const FInstancedStruct& DataInstance)
{
RemovePayloadByType(DataInstance.GetScriptStruct());
Payloads.Add(DataInstance);
}
const FInstancedStruct* FGGA_GameplayEffectContext::FindPayloadByType(const UScriptStruct* PayloadType) const
{
for (const FInstancedStruct& Payload : Payloads)
{
if (const UScriptStruct* CandidateStruct = Payload.GetScriptStruct())
{
if (CandidateStruct == PayloadType)
{
return &Payload;
}
}
}
return nullptr;
}
FInstancedStruct* FGGA_GameplayEffectContext::FindPayloadByType(const UScriptStruct* PayloadType)
{
for (FInstancedStruct& Payload : Payloads)
{
if (const UScriptStruct* CandidateStruct = Payload.GetScriptStruct())
{
if (CandidateStruct == PayloadType)
{
return &Payload;
}
}
}
return nullptr;
}
FInstancedStruct* FGGA_GameplayEffectContext::FindOrAddPayloadByType(const UScriptStruct* PayloadType)
{
if (FInstancedStruct* ExistingData = FindPayloadByType(PayloadType))
{
return ExistingData;
}
return AddPayloadByType(PayloadType);
}
FInstancedStruct* FGGA_GameplayEffectContext::AddPayloadByType(const UScriptStruct* PayloadType)
{
if (ensure(!FindPayloadByType(PayloadType)))
{
FInstancedStruct Data;
Data.InitializeAs(PayloadType);
AddOrOverwriteData(Data);
return FindPayloadByType(PayloadType);
}
return nullptr;
}
bool FGGA_GameplayEffectContext::RemovePayloadByType(const UScriptStruct* PayloadType)
{
int32 IndexToRemove = -1;
for (int32 i = 0; i < Payloads.Num() && IndexToRemove < 0; ++i)
{
if (const UScriptStruct* CandidateStruct = Payloads[i].GetScriptStruct())
{
if (CandidateStruct == PayloadType)
{
IndexToRemove = i;
break;
}
}
}
if (IndexToRemove >= 0)
{
Payloads.RemoveAt(IndexToRemove);
return true;
}
return false;
}

View File

@@ -0,0 +1,49 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Notifies/GGA_AnimNotify_SendGameplayEvent.h"
#include "Components/SkeletalMeshComponent.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemComponent.h"
#include "GameplayTagsManager.h"
void UGGA_AnimNotify_SendGameplayEvent::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
if (EventTag.IsValid() && MeshComp->GetOwner())
{
UAbilitySystemComponent* ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(MeshComp->GetOwner());
if (ASC == nullptr)
return;
FGameplayEventData EventData;
EventData.Instigator = MeshComp->GetOwner();
EventData.EventMagnitude = EventReference.GetNotify()->GetTime();
EventData.EventTag = EventTag;
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(MeshComp->GetOwner(),EventTag,EventData);
}
}
FName GetLastTagName(FGameplayTag Tag)
{
if (!Tag.IsValid())
{
return FName(TEXT("Invalid Tag"));
}
TArray<FName> TagNames;
UGameplayTagsManager::Get().SplitGameplayTagFName(Tag, TagNames);
if (TagNames.IsEmpty())
{
return FName(TEXT("Invalid Tag"));
}
return TagNames.Last();
}
FString UGGA_AnimNotify_SendGameplayEvent::GetNotifyName_Implementation() const
{
FString NotifyName = FString::Format(TEXT("SendEvent:{0}"),{GetLastTagName(EventTag).ToString()});
return NotifyName;
}

View File

@@ -0,0 +1,69 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Phases/GGA_GamePhaseAbility.h"
#include "AbilitySystemComponent.h"
#include "Phases/GGA_GamePhaseSubsystem.h"
#include "Engine/World.h"
#if WITH_EDITOR
#include "Misc/DataValidation.h"
#endif
#include UE_INLINE_GENERATED_CPP_BY_NAME(GGA_GamePhaseAbility)
#define LOCTEXT_NAMESPACE "UGGA_GamePhaseAbility"
UGGA_GamePhaseAbility::UGGA_GamePhaseAbility(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateNo;
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::ServerInitiated;
NetSecurityPolicy = EGameplayAbilityNetSecurityPolicy::ServerOnly;
}
void UGGA_GamePhaseAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData)
{
if (ActorInfo->IsNetAuthority())
{
UWorld* World = ActorInfo->AbilitySystemComponent->GetWorld();
UGGA_GamePhaseSubsystem* PhaseSubsystem = UWorld::GetSubsystem<UGGA_GamePhaseSubsystem>(World);
PhaseSubsystem->OnBeginPhase(this, Handle);
}
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
}
void UGGA_GamePhaseAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
bool bReplicateEndAbility, bool bWasCancelled)
{
if (ActorInfo->IsNetAuthority())
{
UWorld* World = ActorInfo->AbilitySystemComponent->GetWorld();
UGGA_GamePhaseSubsystem* PhaseSubsystem = UWorld::GetSubsystem<UGGA_GamePhaseSubsystem>(World);
PhaseSubsystem->OnEndPhase(this, Handle);
}
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
#if WITH_EDITOR
#if ENGINE_MINOR_VERSION > 2
EDataValidationResult UGGA_GamePhaseAbility::IsDataValid(FDataValidationContext& Context) const
{
EDataValidationResult Result = CombineDataValidationResults(Super::IsDataValid(Context), EDataValidationResult::Valid);
if (!GamePhaseTag.IsValid())
{
Result = EDataValidationResult::Invalid;
Context.AddError(LOCTEXT("GamePhaseTagNotSet", "GamePhaseTag must be set to a tag representing the current phase."));
}
return Result;
}
#endif
#endif
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,6 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Phases/GGA_GamePhaseLog.h"
DEFINE_LOG_CATEGORY(LogGGA_GamePhase)

View File

@@ -0,0 +1,219 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Phases/GGA_GamePhaseSubsystem.h"
#include "Phases/GGA_GamePhaseAbility.h"
#include "GameplayTagsManager.h"
#include "GameFramework/GameState.h"
#include "Engine/World.h"
#include "AbilitySystemComponent.h"
#include "Abilities/GGA_GameplayAbility.h"
#include "GGA_AbilitySystemComponent.h"
#include "Phases/GGA_GamePhaseLog.h"
//////////////////////////////////////////////////////////////////////
// UGGA_GamePhaseSubsystem
UGGA_GamePhaseSubsystem::UGGA_GamePhaseSubsystem()
{
}
bool UGGA_GamePhaseSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
if (Super::ShouldCreateSubsystem(Outer))
{
//UWorld* World = Cast<UWorld>(Outer);
//check(World);
//return World->GetAuthGameMode() != nullptr;
//return nullptr;
return true;
}
return false;
}
bool UGGA_GamePhaseSubsystem::DoesSupportWorldType(const EWorldType::Type WorldType) const
{
return WorldType == EWorldType::Game || WorldType == EWorldType::PIE;
}
void UGGA_GamePhaseSubsystem::StartPhase(TSubclassOf<UGGA_GamePhaseAbility> PhaseAbility, FGGamePhaseDelegate PhaseEndedCallback)
{
UWorld* World = GetWorld();
UAbilitySystemComponent* GameState_ASC = World->GetGameState()->FindComponentByClass<UAbilitySystemComponent>();
if (ensure(GameState_ASC))
{
FGameplayAbilitySpec PhaseSpec(PhaseAbility, 1, 0, this);
FGameplayAbilitySpecHandle SpecHandle = GameState_ASC->GiveAbilityAndActivateOnce(PhaseSpec);
FGameplayAbilitySpec* FoundSpec = GameState_ASC->FindAbilitySpecFromHandle(SpecHandle);
if (FoundSpec && FoundSpec->IsActive())
{
FGGamePhaseEntry& Entry = ActivePhaseMap.FindOrAdd(SpecHandle);
Entry.PhaseEndedCallback = PhaseEndedCallback;
}
else
{
PhaseEndedCallback.ExecuteIfBound(nullptr);
}
}
}
void UGGA_GamePhaseSubsystem::K2_StartPhase(TSubclassOf<UGGA_GamePhaseAbility> PhaseAbility, const FGGamePhaseDynamicDelegate& PhaseEndedDelegate)
{
const FGGamePhaseDelegate EndedDelegate = FGGamePhaseDelegate::CreateWeakLambda(const_cast<UObject*>(PhaseEndedDelegate.GetUObject()), [PhaseEndedDelegate](const UGGA_GamePhaseAbility* PhaseAbility)
{
PhaseEndedDelegate.ExecuteIfBound(PhaseAbility);
});
StartPhase(PhaseAbility, EndedDelegate);
}
void UGGA_GamePhaseSubsystem::K2_WhenPhaseStartsOrIsActive(FGameplayTag PhaseTag, EGGA_PhaseTagMatchType MatchType, FGGamePhaseTagDynamicDelegate WhenPhaseActive)
{
const FGGamePhaseTagDelegate ActiveDelegate = FGGamePhaseTagDelegate::CreateWeakLambda(WhenPhaseActive.GetUObject(), [WhenPhaseActive](const FGameplayTag& PhaseTag)
{
WhenPhaseActive.ExecuteIfBound(PhaseTag);
});
WhenPhaseStartsOrIsActive(PhaseTag, MatchType, ActiveDelegate);
}
void UGGA_GamePhaseSubsystem::K2_WhenPhaseEnds(FGameplayTag PhaseTag, EGGA_PhaseTagMatchType MatchType, FGGamePhaseTagDynamicDelegate WhenPhaseEnd)
{
const FGGamePhaseTagDelegate EndedDelegate = FGGamePhaseTagDelegate::CreateWeakLambda(WhenPhaseEnd.GetUObject(), [WhenPhaseEnd](const FGameplayTag& PhaseTag)
{
WhenPhaseEnd.ExecuteIfBound(PhaseTag);
});
WhenPhaseEnds(PhaseTag, MatchType, EndedDelegate);
}
void UGGA_GamePhaseSubsystem::WhenPhaseStartsOrIsActive(FGameplayTag PhaseTag, EGGA_PhaseTagMatchType MatchType, const FGGamePhaseTagDelegate& WhenPhaseActive)
{
FGPhaseObserver Observer;
Observer.PhaseTag = PhaseTag;
Observer.MatchType = MatchType;
Observer.PhaseCallback = WhenPhaseActive;
PhaseStartObservers.Add(Observer);
if (IsPhaseActive(PhaseTag))
{
WhenPhaseActive.ExecuteIfBound(PhaseTag);
}
}
void UGGA_GamePhaseSubsystem::WhenPhaseEnds(FGameplayTag PhaseTag, EGGA_PhaseTagMatchType MatchType, const FGGamePhaseTagDelegate& WhenPhaseEnd)
{
FGPhaseObserver Observer;
Observer.PhaseTag = PhaseTag;
Observer.MatchType = MatchType;
Observer.PhaseCallback = WhenPhaseEnd;
PhaseEndObservers.Add(Observer);
}
bool UGGA_GamePhaseSubsystem::IsPhaseActive(const FGameplayTag& PhaseTag) const
{
for (const auto& KVP : ActivePhaseMap)
{
const FGGamePhaseEntry& PhaseEntry = KVP.Value;
if (PhaseEntry.PhaseTag.MatchesTag(PhaseTag))
{
return true;
}
}
return false;
}
void UGGA_GamePhaseSubsystem::OnBeginPhase(const UGGA_GamePhaseAbility* PhaseAbility, const FGameplayAbilitySpecHandle PhaseAbilityHandle)
{
const FGameplayTag IncomingPhaseTag = PhaseAbility->GetGamePhaseTag();
const FGameplayTag IncomingPhaseParentTag = UGameplayTagsManager::Get().RequestGameplayTagDirectParent(IncomingPhaseTag);
UE_LOG(LogGGA_GamePhase, Log, TEXT("Beginning Phase '%s' (%s)"), *IncomingPhaseTag.ToString(), *GetNameSafe(PhaseAbility));
const UWorld* World = GetWorld();
UGGA_AbilitySystemComponent* GameState_ASC = World->GetGameState()->FindComponentByClass<UGGA_AbilitySystemComponent>();
if (ensure(GameState_ASC))
{
TArray<FGameplayAbilitySpec*> ActivePhases;
for (const auto& KVP : ActivePhaseMap)
{
const FGameplayAbilitySpecHandle ActiveAbilityHandle = KVP.Key;
if (FGameplayAbilitySpec* Spec = GameState_ASC->FindAbilitySpecFromHandle(ActiveAbilityHandle))
{
ActivePhases.Add(Spec);
}
}
for (const FGameplayAbilitySpec* ActivePhase : ActivePhases)
{
const UGGA_GamePhaseAbility* ActivePhaseAbility = CastChecked<UGGA_GamePhaseAbility>(ActivePhase->Ability);
const FGameplayTag ActivePhaseTag = ActivePhaseAbility->GetGamePhaseTag();
// So if the active phase currently matches the incoming phase tag, we allow it.
// i.e. multiple gameplay abilities can all be associated with the same phase tag.
// For example,
// You can be in the, Game.Playing, phase, and then start a sub-phase, like Game.Playing.SuddenDeath
// Game.Playing phase will still be active, and if someone were to push another one, like,
// Game.Playing.ActualSuddenDeath, it would end Game.Playing.SuddenDeath phase, but Game.Playing would
// continue. Similarly if we activated Game.GameOver, all the Game.Playing* phases would end.
if (!ActivePhaseTag.MatchesTag(IncomingPhaseTag) && ActivePhaseTag.MatchesTag(IncomingPhaseParentTag))
{
UE_LOG(LogGGA_GamePhase, Log, TEXT("\tEnding Phase '%s' (%s)"), *ActivePhaseTag.ToString(), *GetNameSafe(ActivePhaseAbility));
FGameplayAbilitySpecHandle HandleToEnd = ActivePhase->Handle;
GameState_ASC->CancelAbilitiesByFunc([HandleToEnd](const UGameplayAbility* AbilityToCancel, FGameplayAbilitySpecHandle Handle)
{
return Handle == HandleToEnd;
}, true);
}
}
FGGamePhaseEntry& Entry = ActivePhaseMap.FindOrAdd(PhaseAbilityHandle);
Entry.PhaseTag = IncomingPhaseTag;
// Notify all observers of this phase that it has started.
for (int32 i = 0; i < PhaseStartObservers.Num(); i++)
{
if (PhaseStartObservers[i].IsMatch(IncomingPhaseTag))
{
PhaseStartObservers[i].PhaseCallback.ExecuteIfBound(IncomingPhaseTag);
}
}
}
}
void UGGA_GamePhaseSubsystem::OnEndPhase(const UGGA_GamePhaseAbility* PhaseAbility, const FGameplayAbilitySpecHandle PhaseAbilityHandle)
{
const FGameplayTag EndedPhaseTag = PhaseAbility->GetGamePhaseTag();
UE_LOG(LogGGA_GamePhase, Log, TEXT("Ended Phase '%s' (%s)"), *EndedPhaseTag.ToString(), *GetNameSafe(PhaseAbility));
const FGGamePhaseEntry& Entry = ActivePhaseMap.FindChecked(PhaseAbilityHandle);
Entry.PhaseEndedCallback.ExecuteIfBound(PhaseAbility);
ActivePhaseMap.Remove(PhaseAbilityHandle);
// Notify all observers of this phase that it has ended.
for (int32 i = 0; i < PhaseEndObservers.Num(); i++)
{
if (PhaseEndObservers[i].IsMatch(EndedPhaseTag))
{
PhaseEndObservers[i].PhaseCallback.ExecuteIfBound(EndedPhaseTag);
}
}
}
bool UGGA_GamePhaseSubsystem::FGPhaseObserver::IsMatch(const FGameplayTag& ComparePhaseTag) const
{
switch (MatchType)
{
case EGGA_PhaseTagMatchType::ExactMatch:
return ComparePhaseTag == PhaseTag;
case EGGA_PhaseTagMatchType::PartialMatch:
return ComparePhaseTag.MatchesTag(PhaseTag);
}
return false;
}

View File

@@ -0,0 +1,124 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "TargetActors/GGA_AbilityTargetActor_LineTrace.h"
#include "DrawDebugHelpers.h"
#include "GameFramework/PlayerController.h"
AGGA_AbilityTargetActor_LineTrace::AGGA_AbilityTargetActor_LineTrace()
{
}
void AGGA_AbilityTargetActor_LineTrace::Configure(
const FGameplayAbilityTargetingLocationInfo& InStartLocation,
FGameplayTag InAimingTag,
FGameplayTag InAimingRemovalTag,
FCollisionProfileName InTraceProfile,
FGameplayTargetDataFilterHandle InFilter,
TSubclassOf<AGameplayAbilityWorldReticle> InReticleClass,
FWorldReticleParameters InReticleParams,
bool bInIgnoreBlockingHits,
bool bInShouldProduceTargetDataOnServer,
bool bInUsePersistentHitResults,
bool bInDebug,
bool bInTraceAffectsAimPitch,
bool bInTraceFromPlayerViewPoint,
bool bInUseAimingSpreadMod,
float InMaxRange,
float InBaseSpread,
float InAimingSpreadMod,
float InTargetingSpreadIncrement,
float InTargetingSpreadMax,
int32 InMaxHitResultsPerTrace,
int32 InNumberOfTraces)
{
StartLocation = InStartLocation;
AimingTag = InAimingTag;
AimingRemovalTag = InAimingRemovalTag;
TraceProfile = InTraceProfile;
Filter = InFilter;
ReticleClass = InReticleClass;
ReticleParams = InReticleParams;
bIgnoreBlockingHits = bInIgnoreBlockingHits;
ShouldProduceTargetDataOnServer = bInShouldProduceTargetDataOnServer;
bUsePersistentHitResults = bInUsePersistentHitResults;
bDebug = bInDebug;
bTraceAffectsAimPitch = bInTraceAffectsAimPitch;
bTraceFromPlayerViewPoint = bInTraceFromPlayerViewPoint;
bUseAimingSpreadMod = bInUseAimingSpreadMod;
MaxRange = InMaxRange;
BaseSpread = InBaseSpread;
AimingSpreadMod = InAimingSpreadMod;
TargetingSpreadIncrement = InTargetingSpreadIncrement;
TargetingSpreadMax = InTargetingSpreadMax;
MaxHitResultsPerTrace = InMaxHitResultsPerTrace;
NumberOfTraces = InNumberOfTraces;
if (bUsePersistentHitResults)
{
NumberOfTraces = 1;
}
}
void AGGA_AbilityTargetActor_LineTrace::DoTrace(TArray<FHitResult>& HitResults, const UWorld* World, const FGameplayTargetDataFilterHandle FilterHandle, const FVector& Start, const FVector& End, FName ProfileName,
const FCollisionQueryParams Params)
{
LineTraceWithFilter(HitResults, World, FilterHandle, Start, End, ProfileName, Params);
}
void AGGA_AbilityTargetActor_LineTrace::ShowDebugTrace(TArray<FHitResult>& HitResults, EDrawDebugTrace::Type DrawDebugType, float Duration)
{
#if ENABLE_DRAW_DEBUG
if (bDebug)
{
FVector ViewStart = StartLocation.GetTargetingTransform().GetLocation();
FRotator ViewRot;
if (PrimaryPC && bTraceFromPlayerViewPoint)
{
PrimaryPC->GetPlayerViewPoint(ViewStart, ViewRot);
}
FVector TraceEnd = HitResults[0].TraceEnd;
if (NumberOfTraces > 1 || bUsePersistentHitResults)
{
TraceEnd = CurrentTraceEnd;
}
DrawDebugLineTraceMulti(GetWorld(), ViewStart, TraceEnd, DrawDebugType, true, HitResults, FLinearColor::Green, FLinearColor::Red, Duration);
}
#endif
}
#if ENABLE_DRAW_DEBUG
// Copied from KismetTraceUtils.cpp
void AGGA_AbilityTargetActor_LineTrace::DrawDebugLineTraceMulti(const UWorld* World, const FVector& Start, const FVector& End, EDrawDebugTrace::Type DrawDebugType, bool bHit, const TArray<FHitResult>& OutHits,
FLinearColor TraceColor, FLinearColor TraceHitColor, float DrawTime)
{
if (DrawDebugType != EDrawDebugTrace::None)
{
bool bPersistent = DrawDebugType == EDrawDebugTrace::Persistent;
float LifeTime = (DrawDebugType == EDrawDebugTrace::ForDuration) ? DrawTime : 0.f;
// @fixme, draw line with thickness = 2.f?
if (bHit && OutHits.Last().bBlockingHit)
{
// Red up to the blocking hit, green thereafter
FVector const BlockingHitPoint = OutHits.Last().ImpactPoint;
::DrawDebugLine(World, Start, BlockingHitPoint, TraceColor.ToFColor(true), bPersistent, LifeTime);
::DrawDebugLine(World, BlockingHitPoint, End, TraceHitColor.ToFColor(true), bPersistent, LifeTime);
}
else
{
// no hit means all red
::DrawDebugLine(World, Start, End, TraceColor.ToFColor(true), bPersistent, LifeTime);
}
// draw hits
for (int32 HitIdx = 0; HitIdx < OutHits.Num(); ++HitIdx)
{
FHitResult const& Hit = OutHits[HitIdx];
::DrawDebugPoint(World, Hit.ImpactPoint, 16.0f, (Hit.bBlockingHit ? TraceColor.ToFColor(true) : TraceHitColor.ToFColor(true)), bPersistent, LifeTime);
}
}
}
#endif // ENABLE_DRAW_DEBUG

View File

@@ -0,0 +1,187 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "TargetActors/GGA_AbilityTargetActor_SphereTrace.h"
#include "WorldCollision.h"
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
#include "DrawDebugHelpers.h"
AGGA_AbilityTargetActor_SphereTrace::AGGA_AbilityTargetActor_SphereTrace()
{
TraceSphereRadius = 100.0f;
}
void AGGA_AbilityTargetActor_SphereTrace::Configure(
const FGameplayAbilityTargetingLocationInfo& InStartLocation,
FGameplayTag InAimingTag,
FGameplayTag InAimingRemovalTag,
FCollisionProfileName InTraceProfile,
FGameplayTargetDataFilterHandle InFilter,
TSubclassOf<AGameplayAbilityWorldReticle> InReticleClass,
FWorldReticleParameters InReticleParams,
bool bInIgnoreBlockingHits,
bool bInShouldProduceTargetDataOnServer,
bool bInUsePersistentHitResults,
bool bInDebug,
bool bInTraceAffectsAimPitch,
bool bInTraceFromPlayerViewPoint,
bool bInUseAimingSpreadMod,
float InMaxRange,
float InTraceSphereRadius,
float InBaseSpread,
float InAimingSpreadMod,
float InTargetingSpreadIncrement,
float InTargetingSpreadMax,
int32 InMaxHitResultsPerTrace,
int32 InNumberOfTraces)
{
StartLocation = InStartLocation;
AimingTag = InAimingTag;
AimingRemovalTag = InAimingRemovalTag;
TraceProfile = InTraceProfile;
Filter = InFilter;
ReticleClass = InReticleClass;
ReticleParams = InReticleParams;
bIgnoreBlockingHits = bInIgnoreBlockingHits;
ShouldProduceTargetDataOnServer = bInShouldProduceTargetDataOnServer;
bUsePersistentHitResults = bInUsePersistentHitResults;
bDebug = bInDebug;
bTraceAffectsAimPitch = bInTraceAffectsAimPitch;
bTraceFromPlayerViewPoint = bInTraceFromPlayerViewPoint;
bUseAimingSpreadMod = bInUseAimingSpreadMod;
MaxRange = InMaxRange;
TraceSphereRadius = InTraceSphereRadius;
BaseSpread = InBaseSpread;
AimingSpreadMod = InAimingSpreadMod;
TargetingSpreadIncrement = InTargetingSpreadIncrement;
TargetingSpreadMax = InTargetingSpreadMax;
MaxHitResultsPerTrace = InMaxHitResultsPerTrace;
NumberOfTraces = InNumberOfTraces;
if (bUsePersistentHitResults)
{
NumberOfTraces = 1;
}
}
void AGGA_AbilityTargetActor_SphereTrace::SphereTraceWithFilter(TArray<FHitResult>& OutHitResults, const UWorld* World,
const FGameplayTargetDataFilterHandle FilterHandle, const FVector& Start,
const FVector& End, float Radius, FName ProfileName,
const FCollisionQueryParams Params)
{
check(World);
TArray<FHitResult> HitResults;
World->SweepMultiByProfile(HitResults, Start, End, FQuat::Identity, ProfileName,
FCollisionShape::MakeSphere(Radius), Params);
TArray<FHitResult> FilteredHitResults;
// Start param could be player ViewPoint. We want HitResult to always display the StartLocation.
FVector TraceStart = StartLocation.GetTargetingTransform().GetLocation();
for (int32 HitIdx = 0; HitIdx < HitResults.Num(); ++HitIdx)
{
FHitResult& Hit = HitResults[HitIdx];
AActor* HitActor = Hit.GetActor();
if (!HitActor || FilterHandle.FilterPassesForActor(HitActor))
{
Hit.TraceStart = TraceStart;
Hit.TraceEnd = End;
FilteredHitResults.Add(Hit);
}
}
OutHitResults = FilteredHitResults;
return;
}
void AGGA_AbilityTargetActor_SphereTrace::DoTrace(TArray<FHitResult>& HitResults, const UWorld* World,
const FGameplayTargetDataFilterHandle FilterHandle, const FVector& Start,
const FVector& End, FName ProfileName, const FCollisionQueryParams Params)
{
SphereTraceWithFilter(HitResults, World, FilterHandle, Start, End, TraceSphereRadius, ProfileName, Params);
}
void AGGA_AbilityTargetActor_SphereTrace::ShowDebugTrace(TArray<FHitResult>& HitResults, EDrawDebugTrace::Type DrawDebugType,
float Duration)
{
#if ENABLE_DRAW_DEBUG
if (bDebug)
{
FVector ViewStart = StartLocation.GetTargetingTransform().GetLocation();
FRotator ViewRot;
if (PrimaryPC && bTraceFromPlayerViewPoint)
{
PrimaryPC->GetPlayerViewPoint(ViewStart, ViewRot);
}
FVector TraceEnd = HitResults[0].TraceEnd;
if (NumberOfTraces > 1 || bUsePersistentHitResults)
{
TraceEnd = CurrentTraceEnd;
}
DrawDebugSphereTraceMulti(GetWorld(), ViewStart, TraceEnd, TraceSphereRadius, DrawDebugType, true, HitResults,
FLinearColor::Green, FLinearColor::Red, Duration);
}
#endif
}
#if ENABLE_DRAW_DEBUG
// Copied from KismetTraceUtils.cpp
void AGGA_AbilityTargetActor_SphereTrace::DrawDebugSweptSphere(const UWorld* InWorld, FVector const& Start, FVector const& End,
float Radius, FColor const& Color, bool bPersistentLines, float LifeTime,
uint8 DepthPriority)
{
FVector const TraceVec = End - Start;
float const Dist = TraceVec.Size();
FVector const Center = Start + TraceVec * 0.5f;
float const HalfHeight = (Dist * 0.5f) + Radius;
FQuat const CapsuleRot = FRotationMatrix::MakeFromZ(TraceVec).ToQuat();
::DrawDebugCapsule(InWorld, Center, HalfHeight, Radius, CapsuleRot, Color, bPersistentLines, LifeTime,
DepthPriority);
}
void AGGA_AbilityTargetActor_SphereTrace::DrawDebugSphereTraceMulti(const UWorld* World, const FVector& Start, const FVector& End,
float Radius, EDrawDebugTrace::Type DrawDebugType, bool bHit,
const TArray<FHitResult>& OutHits, FLinearColor TraceColor,
FLinearColor TraceHitColor, float DrawTime)
{
if (DrawDebugType != EDrawDebugTrace::None)
{
bool bPersistent = DrawDebugType == EDrawDebugTrace::Persistent;
float LifeTime = (DrawDebugType == EDrawDebugTrace::ForDuration) ? DrawTime : 0.f;
if (bHit && OutHits.Last().bBlockingHit)
{
// Red up to the blocking hit, green thereafter
FVector const BlockingHitPoint = OutHits.Last().Location;
DrawDebugSweptSphere(World, Start, BlockingHitPoint, Radius, TraceColor.ToFColor(true), bPersistent,
LifeTime);
DrawDebugSweptSphere(World, BlockingHitPoint, End, Radius, TraceHitColor.ToFColor(true), bPersistent,
LifeTime);
}
else
{
// no hit means all red
DrawDebugSweptSphere(World, Start, End, Radius, TraceColor.ToFColor(true), bPersistent, LifeTime);
}
// draw hits
for (int32 HitIdx = 0; HitIdx < OutHits.Num(); ++HitIdx)
{
FHitResult const& Hit = OutHits[HitIdx];
::DrawDebugPoint(World, Hit.ImpactPoint, 16.0f,
(Hit.bBlockingHit ? TraceColor.ToFColor(true) : TraceHitColor.ToFColor(true)), bPersistent,
LifeTime);
}
}
}
#endif // ENABLE_DRAW_DEBUG

View File

@@ -0,0 +1,622 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "TargetActors/GGA_AbilityTargetActor_Trace.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystemLog.h"
#include "Engine/World.h"
#include "DrawDebugHelpers.h"
#include "GameFramework/PlayerController.h"
#include "GameplayAbilitySpec.h"
#include "Kismet/KismetMathLibrary.h"
AGGA_AbilityTargetActor_Trace::AGGA_AbilityTargetActor_Trace()
{
bDestroyOnConfirmation = false;
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickGroup = TG_PostUpdateWork;
MaxHitResultsPerTrace = 1;
NumberOfTraces = 1;
bIgnoreBlockingHits = false;
bTraceAffectsAimPitch = true;
bTraceFromPlayerViewPoint = false;
MaxRange = 999999.0f;
bUseAimingSpreadMod = false;
BaseSpread = 0.0f;
AimingSpreadMod = 0.0f;
TargetingSpreadIncrement = 0.0f;
TargetingSpreadMax = 0.0f;
CurrentTargetingSpread = 0.0f;
bUsePersistentHitResults = false;
}
void AGGA_AbilityTargetActor_Trace::ResetSpread()
{
bUseAimingSpreadMod = false;
BaseSpread = 0.0f;
AimingSpreadMod = 0.0f;
TargetingSpreadIncrement = 0.0f;
TargetingSpreadMax = 0.0f;
CurrentTargetingSpread = 0.0f;
}
float AGGA_AbilityTargetActor_Trace::GetCurrentSpread() const
{
float FinalSpread = BaseSpread + CurrentTargetingSpread;
if (bUseAimingSpreadMod && AimingTag.IsValid() && AimingRemovalTag.IsValid())
{
UAbilitySystemComponent* ASC = OwningAbility->GetCurrentActorInfo()->AbilitySystemComponent.Get();
if (ASC && (ASC->GetTagCount(AimingTag) > ASC->GetTagCount(AimingRemovalTag)))
{
FinalSpread *= AimingSpreadMod;
}
}
return FinalSpread;
}
void AGGA_AbilityTargetActor_Trace::SetStartLocation(const FGameplayAbilityTargetingLocationInfo& InStartLocation)
{
StartLocation = InStartLocation;
}
void AGGA_AbilityTargetActor_Trace::SetShouldProduceTargetDataOnServer(bool bInShouldProduceTargetDataOnServer)
{
ShouldProduceTargetDataOnServer = bInShouldProduceTargetDataOnServer;
}
void AGGA_AbilityTargetActor_Trace::SetDestroyOnConfirmation(bool bInDestroyOnConfirmation)
{
bDestroyOnConfirmation = bInDestroyOnConfirmation;
}
void AGGA_AbilityTargetActor_Trace::StartTargeting(UGameplayAbility* Ability)
{
// Don't call to Super because we can have more than one Reticle
SetActorTickEnabled(true);
OwningAbility = Ability;
SourceActor = Ability->GetCurrentActorInfo()->AvatarActor.Get();
// This is a lazy way of emptying and repopulating the ReticleActors.
// We could come up with a solution that reuses them.
DestroyReticleActors();
if (ReticleClass)
{
for (int32 i = 0; i < MaxHitResultsPerTrace * NumberOfTraces; i++)
{
SpawnReticleActor(GetActorLocation(), GetActorRotation());
}
}
if (bUsePersistentHitResults)
{
PersistentHitResults.Empty();
}
}
void AGGA_AbilityTargetActor_Trace::ConfirmTargetingAndContinue()
{
check(ShouldProduceTargetData());
if (SourceActor)
{
TArray<FHitResult> HitResults = PerformTrace(SourceActor);
FGameplayAbilityTargetDataHandle Handle = MakeTargetData(HitResults);
TargetDataReadyDelegate.Broadcast(Handle);
#if ENABLE_DRAW_DEBUG
if (bDebug)
{
ShowDebugTrace(HitResults, EDrawDebugTrace::Type::ForDuration, 2.0f);
}
#endif
}
if (bUsePersistentHitResults)
{
PersistentHitResults.Empty();
}
}
void AGGA_AbilityTargetActor_Trace::CancelTargeting()
{
const FGameplayAbilityActorInfo* ActorInfo = (OwningAbility ? OwningAbility->GetCurrentActorInfo() : nullptr);
UAbilitySystemComponent* ASC = (ActorInfo ? ActorInfo->AbilitySystemComponent.Get() : nullptr);
if (ASC)
{
ASC->AbilityReplicatedEventDelegate(EAbilityGenericReplicatedEvent::GenericCancel,
OwningAbility->GetCurrentAbilitySpecHandle(),
OwningAbility->GetCurrentActivationInfo().GetActivationPredictionKey()).
Remove(GenericCancelHandle);
}
else
{
ABILITY_LOG(Warning, TEXT("AGameplayAbilityTargetActor::CancelTargeting called with null ASC! Actor %s"),
*GetName());
}
CanceledDelegate.Broadcast(FGameplayAbilityTargetDataHandle());
SetActorTickEnabled(false);
if (bUsePersistentHitResults)
{
PersistentHitResults.Empty();
}
}
void AGGA_AbilityTargetActor_Trace::BeginPlay()
{
Super::BeginPlay();
// 一开始就禁用Tick。我们会在StartTargeting中启用并在StopTargeting中禁用。
// 对于瞬间确认tick永远不会发生因为我们一次性StartConfirm然后马上Stop。
SetActorTickEnabled(false);
}
void AGGA_AbilityTargetActor_Trace::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
DestroyReticleActors();
Super::EndPlay(EndPlayReason);
}
void AGGA_AbilityTargetActor_Trace::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
TArray<FHitResult> HitResults;
if (bDebug || bUsePersistentHitResults)
{
// Only need to trace on Tick if we're showing debug or if we use persistent hit results, otherwise we just use the confirmation trace
HitResults = PerformTrace(SourceActor);
if (HitResults.Num() > 0)
{
const FVector StartPoint = StartLocation.GetTargetingTransform().GetLocation();
const FVector EndPoint = HitResults[0].Component.IsValid() ? HitResults[0].ImpactPoint : HitResults[0].TraceEnd;
const FRotator Rotator = UKismetMathLibrary::FindLookAtRotation(StartPoint, EndPoint);
SetActorLocationAndRotation(StartPoint, Rotator);
}
}
#if ENABLE_DRAW_DEBUG
if (SourceActor && bDebug)
{
ShowDebugTrace(HitResults, EDrawDebugTrace::Type::ForOneFrame);
}
#endif
}
void AGGA_AbilityTargetActor_Trace::LineTraceWithFilter(TArray<FHitResult>& OutHitResults, const UWorld* World,
const FGameplayTargetDataFilterHandle FilterHandle, const FVector& Start,
const FVector& End, FName ProfileName, const FCollisionQueryParams Params)
{
check(World);
TArray<FHitResult> HitResults;
World->LineTraceMultiByProfile(HitResults, Start, End, ProfileName, Params);
TArray<FHitResult> FilteredHitResults;
// Start param could be player ViewPoint. We want HitResult to always display the StartLocation.
FVector TraceStart = StartLocation.GetTargetingTransform().GetLocation();
for (int32 HitIdx = 0; HitIdx < HitResults.Num(); ++HitIdx)
{
FHitResult& Hit = HitResults[HitIdx];
AActor* HitActor = Hit.GetActor();
if (!HitActor || FilterHandle.FilterPassesForActor(HitActor))
{
Hit.TraceStart = TraceStart;
Hit.TraceEnd = End;
FilteredHitResults.Add(Hit);
}
}
OutHitResults = FilteredHitResults;
}
void AGGA_AbilityTargetActor_Trace::AimWithPlayerController(const AActor* InSourceActor, FCollisionQueryParams Params,
const FVector& TraceStart, FVector& OutTraceEnd, bool bIgnorePitch)
{
if (!OwningAbility) // Server and launching client only
{
return;
}
// Default values in case of AI Controller
FVector ViewStart = TraceStart;
FRotator ViewRot = StartLocation.GetTargetingTransform().GetRotation().Rotator();
if (PrimaryPC && bTraceFromPlayerViewPoint)
{
PrimaryPC->GetPlayerViewPoint(ViewStart, ViewRot);
}
const FVector ViewDir = ViewRot.Vector();
FVector ViewEnd = ViewStart + (ViewDir * MaxRange);
ClipCameraRayToAbilityRange(ViewStart, ViewDir, TraceStart, MaxRange, ViewEnd);
// Use first hit
TArray<FHitResult> HitResults;
LineTraceWithFilter(HitResults, InSourceActor->GetWorld(), Filter, ViewStart, ViewEnd, TraceProfile.Name, Params);
CurrentTargetingSpread = FMath::Min(TargetingSpreadMax, CurrentTargetingSpread + TargetingSpreadIncrement);
const bool bUseTraceResult = HitResults.Num() > 0 && (FVector::DistSquared(TraceStart, HitResults[0].Location) <= (
MaxRange * MaxRange));
const FVector AdjustedEnd = (bUseTraceResult) ? HitResults[0].Location : ViewEnd;
FVector AdjustedAimDir = (AdjustedEnd - TraceStart).GetSafeNormal();
if (AdjustedAimDir.IsZero())
{
AdjustedAimDir = ViewDir;
}
if (!bTraceAffectsAimPitch && bUseTraceResult)
{
FVector OriginalAimDir = (ViewEnd - TraceStart).GetSafeNormal();
if (!OriginalAimDir.IsZero())
{
// Convert to angles and use original pitch
const FRotator OriginalAimRot = OriginalAimDir.Rotation();
FRotator AdjustedAimRot = AdjustedAimDir.Rotation();
AdjustedAimRot.Pitch = OriginalAimRot.Pitch;
AdjustedAimDir = AdjustedAimRot.Vector();
}
}
const float CurrentSpread = GetCurrentSpread();
const float ConeHalfAngle = FMath::DegreesToRadians(CurrentSpread * 0.5f);
const int32 RandomSeed = FMath::Rand();
FRandomStream WeaponRandomStream(RandomSeed);
const FVector ShootDir = WeaponRandomStream.VRandCone(AdjustedAimDir, ConeHalfAngle, ConeHalfAngle);
OutTraceEnd = TraceStart + (ShootDir * MaxRange);
}
bool AGGA_AbilityTargetActor_Trace::ClipCameraRayToAbilityRange(FVector CameraLocation, FVector CameraDirection, FVector AbilityCenter,
float AbilityRange, FVector& ClippedPosition)
{
FVector CameraToCenter = AbilityCenter - CameraLocation;
float DotToCenter = FVector::DotProduct(CameraToCenter, CameraDirection);
if (DotToCenter >= 0)
//If this fails, we're pointed away from the center, but we might be inside the sphere and able to find a good exit point.
{
float DistanceSquared = CameraToCenter.SizeSquared() - (DotToCenter * DotToCenter);
float RadiusSquared = (AbilityRange * AbilityRange);
if (DistanceSquared <= RadiusSquared)
{
float DistanceFromCamera = FMath::Sqrt(RadiusSquared - DistanceSquared);
float DistanceAlongRay = DotToCenter + DistanceFromCamera;
//Subtracting instead of adding will get the other intersection point
ClippedPosition = CameraLocation + (DistanceAlongRay * CameraDirection);
//Cam aim point clipped to range sphere
return true;
}
}
return false;
}
void AGGA_AbilityTargetActor_Trace::StopTargeting()
{
SetActorTickEnabled(false);
DestroyReticleActors();
// Clear added callbacks
TargetDataReadyDelegate.Clear();
CanceledDelegate.Clear();
if (GenericDelegateBoundASC)
{
GenericDelegateBoundASC->GenericLocalConfirmCallbacks.RemoveDynamic(
this, &AGameplayAbilityTargetActor::ConfirmTargeting);
GenericDelegateBoundASC->GenericLocalCancelCallbacks.RemoveDynamic(
this, &AGameplayAbilityTargetActor::CancelTargeting);
GenericDelegateBoundASC = nullptr;
}
}
FGameplayAbilityTargetDataHandle AGGA_AbilityTargetActor_Trace::MakeTargetData(const TArray<FHitResult>& HitResults) const
{
FGameplayAbilityTargetDataHandle ReturnDataHandle;
for (int32 i = 0; i < HitResults.Num(); i++)
{
/** Note: These are cleaned up by the FGameplayAbilityTargetDataHandle (via an internal TSharedPtr) */
FGameplayAbilityTargetData_SingleTargetHit* ReturnData = new FGameplayAbilityTargetData_SingleTargetHit();
ReturnData->HitResult = HitResults[i];
ReturnDataHandle.Add(ReturnData);
}
return ReturnDataHandle;
}
TArray<FHitResult> AGGA_AbilityTargetActor_Trace::PerformTrace(AActor* InSourceActor)
{
bool bTraceComplex = false;
TArray<AActor*> ActorsToIgnore;
ActorsToIgnore.Add(InSourceActor);
FCollisionQueryParams Params(SCENE_QUERY_STAT(AGSGATA_LineTrace), bTraceComplex);
Params.bReturnPhysicalMaterial = true;
Params.AddIgnoredActors(ActorsToIgnore);
Params.bIgnoreBlocks = bIgnoreBlockingHits;
FVector TraceStart = StartLocation.GetTargetingTransform().GetLocation();
FVector TraceEnd;
if (PrimaryPC)
{
FVector ViewStart;
FRotator ViewRot;
PrimaryPC->GetPlayerViewPoint(ViewStart, ViewRot);
TraceStart = bTraceFromPlayerViewPoint ? ViewStart : TraceStart;
}
if (bUsePersistentHitResults)
{
// Clear any blocking hit results, invalid Actors, or actors out of range
//TODO Check for visibility if we add AIPerceptionComponent in the future
for (int32 i = PersistentHitResults.Num() - 1; i >= 0; i--)
{
FHitResult& HitResult = PersistentHitResults[i];
AActor* HitActor = HitResult.GetActor();
if (HitResult.bBlockingHit || !HitActor || FVector::DistSquared(
TraceStart, HitActor->GetActorLocation()) > (MaxRange * MaxRange))
{
PersistentHitResults.RemoveAt(i);
}
}
}
TArray<FHitResult> ReturnHitResults;
for (int32 TraceIndex = 0; TraceIndex < NumberOfTraces; TraceIndex++)
{
AimWithPlayerController(InSourceActor, Params, TraceStart, TraceEnd);
//Effective on server and launching client only
// ------------------------------------------------------
SetActorLocationAndRotation(TraceEnd, SourceActor->GetActorRotation());
CurrentTraceEnd = TraceEnd;
TArray<FHitResult> TraceHitResults;
DoTrace(TraceHitResults, InSourceActor->GetWorld(), Filter, TraceStart, TraceEnd, TraceProfile.Name, Params);
for (int32 j = TraceHitResults.Num() - 1; j >= 0; j--)
{
if (MaxHitResultsPerTrace >= 0 && j + 1 > MaxHitResultsPerTrace)
{
// Trim to MaxHitResultsPerTrace
TraceHitResults.RemoveAt(j);
continue;
}
FHitResult& HitResult = TraceHitResults[j];
// Reminder: if bUsePersistentHitResults, Number of Traces = 1
if (bUsePersistentHitResults)
{
AActor* HitActor = HitResult.GetActor();
// This is looping backwards so that further objects from player are added first to the queue.
// This results in closer actors taking precedence as the further actors will get bumped out of the TArray.
if (HitActor && (!HitResult.bBlockingHit || PersistentHitResults.Num() < 1))
{
bool bActorAlreadyInPersistentHits = false;
// Make sure PersistentHitResults doesn't have this hit actor already
for (int32 k = 0; k < PersistentHitResults.Num(); k++)
{
FHitResult& PersistentHitResult = PersistentHitResults[k];
AActor* PersistentHitActor = PersistentHitResult.GetActor();
if (PersistentHitActor == HitActor)
{
bActorAlreadyInPersistentHits = true;
break;
}
}
if (bActorAlreadyInPersistentHits)
{
continue;
}
if (PersistentHitResults.Num() >= MaxHitResultsPerTrace)
{
// Treat PersistentHitResults like a queue, remove first element
PersistentHitResults.RemoveAt(0);
}
PersistentHitResults.Add(HitResult);
}
}
else
{
// ReticleActors for PersistentHitResults are handled later
int32 ReticleIndex = TraceIndex * MaxHitResultsPerTrace + j;
if (ReticleIndex < ReticleActors.Num())
{
if (AGameplayAbilityWorldReticle* LocalReticleActor = ReticleActors[ReticleIndex].Get())
{
AActor* HitActor = HitResult.GetActor();
const bool bHitActor = HitActor != nullptr;
if (bHitActor && !HitResult.bBlockingHit)
{
LocalReticleActor->SetActorHiddenInGame(false);
const FVector ReticleLocation = (bHitActor && LocalReticleActor->bSnapToTargetedActor)
? HitActor->GetActorLocation()
: HitResult.Location;
LocalReticleActor->SetActorLocation(ReticleLocation);
LocalReticleActor->SetIsTargetAnActor(bHitActor);
}
else
{
LocalReticleActor->SetActorHiddenInGame(true);
}
}
}
}
} // for TraceHitResults
if (!bUsePersistentHitResults)
{
if (TraceHitResults.Num() < ReticleActors.Num())
{
// We have less hit results than ReticleActors, hide the extra ones
for (int32 j = TraceHitResults.Num(); j < ReticleActors.Num(); j++)
{
if (AGameplayAbilityWorldReticle* LocalReticleActor = ReticleActors[j].Get())
{
LocalReticleActor->SetIsTargetAnActor(false);
LocalReticleActor->SetActorHiddenInGame(true);
}
}
}
}
if (TraceHitResults.Num() < 1)
{
// If there were no hits, add a default HitResult at the end of the trace
FHitResult HitResult;
// Start param could be player ViewPoint. We want HitResult to always display the StartLocation.
HitResult.TraceStart = StartLocation.GetTargetingTransform().GetLocation();
HitResult.TraceEnd = TraceEnd;
HitResult.Location = TraceEnd;
HitResult.ImpactPoint = TraceEnd;
TraceHitResults.Add(HitResult);
if (bUsePersistentHitResults && PersistentHitResults.Num() < 1)
{
PersistentHitResults.Add(HitResult);
}
}
ReturnHitResults.Append(TraceHitResults);
} // for NumberOfTraces
// Reminder: if bUsePersistentHitResults, Number of Traces = 1
if (bUsePersistentHitResults && MaxHitResultsPerTrace > 0)
{
// Handle ReticleActors
for (int32 PersistentHitResultIndex = 0; PersistentHitResultIndex < PersistentHitResults.Num();
PersistentHitResultIndex++)
{
FHitResult& HitResult = PersistentHitResults[PersistentHitResultIndex];
AActor* HitActor = HitResult.GetActor();
// Update TraceStart because old persistent HitResults will have their original TraceStart and the player could have moved since then
HitResult.TraceStart = StartLocation.GetTargetingTransform().GetLocation();
if (!ReticleActors.IsValidIndex(PersistentHitResultIndex))
{
continue;
}
if (AGameplayAbilityWorldReticle* LocalReticleActor = ReticleActors[PersistentHitResultIndex].Get())
{
const bool bHitActor = HitActor != nullptr;
if (bHitActor && !HitResult.bBlockingHit)
{
LocalReticleActor->SetActorHiddenInGame(false);
const FVector ReticleLocation = (bHitActor && LocalReticleActor->bSnapToTargetedActor)
? HitActor->GetActorLocation()
: HitResult.Location;
LocalReticleActor->SetActorLocation(ReticleLocation);
LocalReticleActor->SetIsTargetAnActor(bHitActor);
}
else
{
LocalReticleActor->SetActorHiddenInGame(true);
}
}
}
if (PersistentHitResults.Num() < ReticleActors.Num())
{
// We have less hit results than ReticleActors, hide the extra ones
for (int32 PersistentHitResultIndex = PersistentHitResults.Num(); PersistentHitResultIndex < ReticleActors.
Num(); PersistentHitResultIndex++)
{
if (AGameplayAbilityWorldReticle* LocalReticleActor = ReticleActors[PersistentHitResultIndex].Get())
{
LocalReticleActor->SetIsTargetAnActor(false);
LocalReticleActor->SetActorHiddenInGame(true);
}
}
}
CurrentHitResults = PersistentHitResults;
return PersistentHitResults;
}
CurrentHitResults = ReturnHitResults;
return ReturnHitResults;
}
AGameplayAbilityWorldReticle* AGGA_AbilityTargetActor_Trace::SpawnReticleActor(FVector Location, FRotator Rotation)
{
if (ReticleClass)
{
AGameplayAbilityWorldReticle* SpawnedReticleActor = GetWorld()->SpawnActor<AGameplayAbilityWorldReticle>(
ReticleClass, Location, Rotation);
if (SpawnedReticleActor)
{
SpawnedReticleActor->InitializeReticle(this, PrimaryPC, ReticleParams);
SpawnedReticleActor->SetActorHiddenInGame(true);
ReticleActors.Add(SpawnedReticleActor);
// This is to catch cases of playing on a listen server where we are using a replicated reticle actor.
// (In a client controlled player, this would only run on the client and therefor never replicate. If it runs
// on a listen server, the reticle actor may replicate. We want consistancy between client/listen server players.
// Just saying 'make the reticle actor non replicated' isnt a good answer, since we want to mix and match reticle
// actors and there may be other targeting types that want to replicate the same reticle actor class).
if (!ShouldProduceTargetDataOnServer)
{
SpawnedReticleActor->SetReplicates(false);
}
return SpawnedReticleActor;
}
}
return nullptr;
}
void AGGA_AbilityTargetActor_Trace::DestroyReticleActors()
{
for (int32 i = ReticleActors.Num() - 1; i >= 0; i--)
{
if (ReticleActors[i].IsValid())
{
ReticleActors[i].Get()->Destroy();
}
}
ReticleActors.Empty();
}
void AGGA_AbilityTargetActor_Trace::GetCurrentHitResult(TArray<FHitResult>& HitResults)
{
HitResults = CurrentHitResults;
}

View File

@@ -0,0 +1,8 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "TargetTypes/GGA_TargetType.h"
void UGGA_TargetType::GetTargets_Implementation(AActor* TargetingActor, FGameplayEventData EventData, TArray<FHitResult>& OutHitResults, TArray<AActor*>& OutActors) const
{
}

View File

@@ -0,0 +1,27 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "TargetTypes/GGA_TargetType_UseEventData.h"
#include "AbilitySystemBlueprintLibrary.h"
void UGGA_TargetType_UseEventData::GetTargets_Implementation(AActor* TargetingActor, FGameplayEventData EventData, TArray<FHitResult>& OutHitResults, TArray<AActor*>& OutActors) const
{
const FHitResult* FoundHitResult = EventData.ContextHandle.GetHitResult();
const FHitResult TargetDataHitResult = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(EventData.TargetData, 0);
if (FoundHitResult)
{
OutHitResults.Add(*FoundHitResult);
}
else if (TargetDataHitResult.IsValidBlockingHit())
{
OutHitResults.Add(TargetDataHitResult);
}
else if (EventData.Target)
{
const AActor* Actor = EventData.Target;
OutActors.Add(const_cast<AActor*>(Actor));
}
}

View File

@@ -0,0 +1,10 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "TargetTypes/GGA_TargetType_UseOwner.h"
void UGGA_TargetType_UseOwner::GetTargets_Implementation(AActor* TargetingActor, FGameplayEventData EventData, TArray<FHitResult>& OutHitResults, TArray<AActor*>& OutActors) const
{
OutActors.Add(TargetingActor);
}

View File

@@ -0,0 +1,947 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Utilities/GGA_AbilitySystemFunctionLibrary.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystemGlobals.h"
#include "Runtime/Launch/Resources/Version.h"
#include "Logging/LogMacros.h"
#include "AbilitySystemLog.h"
#include "GameplayCueManager.h"
#include "Animation/AnimSequenceBase.h"
#include "Animation/AnimMontage.h"
bool UGGA_AbilitySystemFunctionLibrary::FindAbilitySystemComponent(AActor* Actor, TSubclassOf<UAbilitySystemComponent> DesiredClass, UAbilitySystemComponent*& ASC)
{
if (UAbilitySystemComponent* Instance = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Actor))
{
if (Instance->GetClass()->IsChildOf(DesiredClass))
{
ASC = Instance;
return true;
}
}
return false;
}
UAbilitySystemComponent* UGGA_AbilitySystemFunctionLibrary::GetAbilitySystemComponent(AActor* Actor, TSubclassOf<UAbilitySystemComponent> DesiredClass)
{
if (UAbilitySystemComponent* Instance = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Actor))
{
if (Instance->GetClass()->IsChildOf(DesiredClass))
{
return Instance;
}
}
return nullptr;
}
void UGGA_AbilitySystemFunctionLibrary::InitAbilityActorInfo(UAbilitySystemComponent* AbilitySystem, AActor* InOwnerActor, AActor* InAvatarActor)
{
if (!IsValid(AbilitySystem) || !IsValid(InOwnerActor) || !IsValid(InAvatarActor))
{
return;
}
AbilitySystem->InitAbilityActorInfo(InOwnerActor, InAvatarActor);
}
AActor* UGGA_AbilitySystemFunctionLibrary::GetOwnerActor(UAbilitySystemComponent* AbilitySystem)
{
if (!IsValid(AbilitySystem))
{
return nullptr;
}
return AbilitySystem->GetOwnerActor();
}
AActor* UGGA_AbilitySystemFunctionLibrary::GetAvatarActor(UAbilitySystemComponent* AbilitySystem)
{
if (!IsValid(AbilitySystem))
{
return nullptr;
}
return AbilitySystem->GetAvatarActor_Direct();
}
int32 UGGA_AbilitySystemFunctionLibrary::HandleGameplayEvent(UAbilitySystemComponent* AbilitySystem, FGameplayTag EventTag, const FGameplayEventData& Payload)
{
if (!IsValid(AbilitySystem) || !EventTag.IsValid())
{
return false;
}
return AbilitySystem->HandleGameplayEvent(EventTag, &Payload);
}
bool UGGA_AbilitySystemFunctionLibrary::TryActivateAbilities(UAbilitySystemComponent* AbilitySystem, TArray<FGameplayAbilitySpecHandle> AbilitiesToActivate, bool bAllowRemoteActivation,
bool bFirstOnly)
{
if (!IsValid(AbilitySystem) || AbilitiesToActivate.IsEmpty())
{
return false;
}
int32 Num = 0;
for (int32 i = 0; i < AbilitiesToActivate.Num(); i++)
{
if (AbilitySystem->TryActivateAbility(AbilitiesToActivate[i], bAllowRemoteActivation))
{
Num++;
if (bFirstOnly)
{
return true;
}
}
}
return Num == AbilitiesToActivate.Num();
}
bool UGGA_AbilitySystemFunctionLibrary::HasActivatableTriggeredAbility(UAbilitySystemComponent* AbilitySystem, FGameplayTag Tag)
{
if (!IsValid(AbilitySystem) || !Tag.IsValid())
{
return false;
}
return AbilitySystem->HasActivatableTriggeredAbility(Tag);
}
void UGGA_AbilitySystemFunctionLibrary::GetActivatableGameplayAbilitySpecsByAllMatchingTags(const UAbilitySystemComponent* AbilitySystem, const FGameplayTagContainer& Tags,
TArray<FGameplayAbilitySpecHandle>& MatchingGameplayAbilities,
bool bOnlyAbilitiesThatSatisfyTagRequirements)
{
if (!IsValid(AbilitySystem) || Tags.IsEmpty())
{
return;
}
MatchingGameplayAbilities.Empty();
TArray<FGameplayAbilitySpec*> AbilitiesToActivate;
AbilitySystem->GetActivatableGameplayAbilitySpecsByAllMatchingTags(Tags, AbilitiesToActivate, bOnlyAbilitiesThatSatisfyTagRequirements);
for (FGameplayAbilitySpec* GameplayAbilitySpec : AbilitiesToActivate)
{
MatchingGameplayAbilities.Add(GameplayAbilitySpec->Handle);
}
}
void UGGA_AbilitySystemFunctionLibrary::GetActivatableGameplayAbilitySpecs(const UAbilitySystemComponent* AbilitySystem, const FGameplayTagContainer& Tags, const UObject* SourceObject,
TArray<FGameplayAbilitySpecHandle>& MatchingGameplayAbilities, bool bOnlyAbilitiesThatSatisfyTagRequirements)
{
if (SourceObject == nullptr)
{
GetActivatableGameplayAbilitySpecsByAllMatchingTags(AbilitySystem, Tags, MatchingGameplayAbilities, bOnlyAbilitiesThatSatisfyTagRequirements);
return;
}
if (!IsValid(AbilitySystem) || Tags.IsEmpty())
{
return;
}
TArray<FGameplayAbilitySpec*> AbilitiesToActivate;
for (const FGameplayAbilitySpec& Spec : AbilitySystem->GetActivatableAbilities())
{
if (Spec.Ability && Spec.Ability->GetAssetTags().HasAll(Tags) && Spec.SourceObject == SourceObject)
{
// Consider abilities that are blocked by tags currently if we're supposed to (default behavior).
// That way, we can use the blocking to find an appropriate ability based on tags when we have more than
// one ability that match the GameplayTagContainer.
if (!bOnlyAbilitiesThatSatisfyTagRequirements || Spec.Ability->DoesAbilitySatisfyTagRequirements(*AbilitySystem))
{
MatchingGameplayAbilities.Add(Spec.Handle);
}
}
}
}
bool UGGA_AbilitySystemFunctionLibrary::GetFirstActivatableAbility(const UAbilitySystemComponent* AbilitySystem, FGameplayTagContainer Tags, FGameplayAbilitySpecHandle& MatchingGameplayAbility,
const UObject* SourceObject, bool bOnlyAbilitiesThatSatisfyTagRequirements)
{
if (SourceObject == nullptr)
{
return GetFirstActivatableAbilityByAllMatchingTags(AbilitySystem, Tags, MatchingGameplayAbility, bOnlyAbilitiesThatSatisfyTagRequirements);
}
if (!IsValid(AbilitySystem) || Tags.IsEmpty())
{
return false;
}
for (const FGameplayAbilitySpec& Spec : AbilitySystem->GetActivatableAbilities())
{
if (Spec.Ability && Spec.Ability->GetAssetTags().HasAll(Tags) && SourceObject == Spec.SourceObject)
{
// Consider abilities that are blocked by tags currently if we're supposed to (default behavior).
// That way, we can use the blocking to find an appropriate ability based on tags when we have more than
// one ability that match the GameplayTagContainer.
if (!bOnlyAbilitiesThatSatisfyTagRequirements || Spec.Ability->DoesAbilitySatisfyTagRequirements(*AbilitySystem))
{
MatchingGameplayAbility = Spec.Handle;
return true;
}
}
}
return false;
}
bool UGGA_AbilitySystemFunctionLibrary::GetFirstActivatableAbilityByAllMatchingTags(const UAbilitySystemComponent* AbilitySystem, FGameplayTagContainer Tags,
FGameplayAbilitySpecHandle& MatchingGameplayAbility, bool bOnlyAbilitiesThatSatisfyTagRequirements)
{
if (!IsValid(AbilitySystem) || Tags.IsEmpty())
{
return false;
}
for (const FGameplayAbilitySpec& Spec : AbilitySystem->GetActivatableAbilities())
{
if (Spec.Ability && Spec.Ability->GetAssetTags().HasAll(Tags))
{
// Consider abilities that are blocked by tags currently if we're supposed to (default behavior).
// That way, we can use the blocking to find an appropriate ability based on tags when we have more than
// one ability that match the GameplayTagContainer.
if (!bOnlyAbilitiesThatSatisfyTagRequirements || Spec.Ability->DoesAbilitySatisfyTagRequirements(*AbilitySystem))
{
MatchingGameplayAbility = Spec.Handle;
return true;
}
}
}
return false;
}
void UGGA_AbilitySystemFunctionLibrary::GetActiveAbilityInstancesWithTags(const UAbilitySystemComponent* AbilitySystem, const FGameplayTagContainer& Tags,
TArray<UGameplayAbility*>& MatchingAbilityInstances)
{
if (!IsValid(AbilitySystem) || Tags.IsEmpty())
{
return;
}
TArray<FGameplayAbilitySpec*> AbilitiesToActivate;
AbilitySystem->GetActivatableGameplayAbilitySpecsByAllMatchingTags(Tags, AbilitiesToActivate, false);
// Iterate the list of all ability specs
for (FGameplayAbilitySpec* Spec : AbilitiesToActivate)
{
// Iterate all instances on this ability spec
TArray<UGameplayAbility*> AbilityInstances = Spec->GetAbilityInstances();
for (UGameplayAbility* ActiveAbility : AbilityInstances)
{
if (ActiveAbility->IsActive())
{
MatchingAbilityInstances.Add(ActiveAbility);
}
}
}
}
bool UGGA_AbilitySystemFunctionLibrary::SendGameplayEventToActor(AActor* Actor, FGameplayTag EventTag, FGameplayEventData Payload)
{
if (IsValid(Actor))
{
UAbilitySystemComponent* AbilitySystemComponent = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(Actor);
if (IsValid(AbilitySystemComponent))
{
FScopedPredictionWindow NewScopedWindow(AbilitySystemComponent, true);
if (AbilitySystemComponent->HandleGameplayEvent(EventTag, &Payload) > 0)
{
return true;
}
}
else
{
ABILITY_LOG(Error, TEXT("UAbilitySystemBPLibrary::SendGameplayEventToActor: Invalid ability system component retrieved from Actor %s. EventTag was %s"), *Actor->GetName(),
*EventTag.ToString());
}
}
return false;
}
void UGGA_AbilitySystemFunctionLibrary::BreakAbilityEndedData(const FAbilityEndedData& AbilityEndedData, UGameplayAbility*& AbilityThatEnded, FGameplayAbilitySpecHandle& AbilitySpecHandle,
bool& bReplicateEndAbility, bool& bWasCancelled)
{
AbilityThatEnded = AbilityEndedData.AbilityThatEnded;
AbilitySpecHandle = AbilityEndedData.AbilitySpecHandle;
bReplicateEndAbility = AbilityEndedData.bReplicateEndAbility;
bWasCancelled = AbilityEndedData.bWasCancelled;
}
bool UGGA_AbilitySystemFunctionLibrary::FindAllAbilitiesWithTagsInOrder(const UAbilitySystemComponent* AbilitySystem, TArray<FGameplayTag> Tags, TArray<FGameplayAbilitySpecHandle>& OutAbilityHandles,
bool bExactMatch)
{
if (!IsValid(AbilitySystem) || Tags.IsEmpty())
{
return false;
}
// ensure the output array is empty
OutAbilityHandles.Empty();
for (const FGameplayTag& Tag : Tags)
{
TArray<FGameplayAbilitySpecHandle> Handles;
AbilitySystem->FindAllAbilitiesWithTags(Handles, Tag.GetSingleTagContainer(), bExactMatch);
OutAbilityHandles.Append(Handles);
}
return !OutAbilityHandles.IsEmpty();
}
bool UGGA_AbilitySystemFunctionLibrary::FindAbilityMatchingQuery(const UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle& OutAbilityHandle, FGameplayTagQuery Query,
const UObject* SourceObject)
{
if (!IsValid(AbilitySystem) || Query.IsEmpty())
{
return false;
}
// iterate through all Ability Specs
for (const FGameplayAbilitySpec& CurrentSpec : AbilitySystem->GetActivatableAbilities())
{
// try to get the ability instance
UGameplayAbility* AbilityInstance = CurrentSpec.GetPrimaryInstance();
// default to the CDO if we can't
if (!AbilityInstance)
{
AbilityInstance = CurrentSpec.Ability;
}
if (SourceObject && CurrentSpec.SourceObject != SourceObject)
{
continue;
}
// ensure the ability instance is valid
if (IsValid(AbilityInstance) && AbilityInstance->GetAssetTags().MatchesQuery(Query))
{
OutAbilityHandle = CurrentSpec.Handle;
return true;
}
}
return false;
}
FGameplayAbilitySpec* UGGA_AbilitySystemFunctionLibrary::FindAbilitySpecFromClass(const UAbilitySystemComponent* AbilitySystem, TSubclassOf<UGameplayAbility> AbilityClass, const UObject* SourceObject)
{
if (!IsValid(AbilitySystem) || AbilityClass == nullptr)
{
return nullptr;
}
for (const FGameplayAbilitySpec& Spec : AbilitySystem->GetActivatableAbilities())
{
if (Spec.Ability == nullptr)
{
continue;
}
if (SourceObject && Spec.SourceObject != SourceObject)
{
continue;
}
if (Spec.Ability->GetClass() == AbilityClass)
{
return const_cast<FGameplayAbilitySpec*>(&Spec);
}
}
return nullptr;
}
bool UGGA_AbilitySystemFunctionLibrary::FindAbilityFromClass(const UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle& OutAbilityHandle, TSubclassOf<UGameplayAbility> AbilityClass,
const UObject* SourceObject)
{
if (!IsValid(AbilitySystem) || AbilityClass == nullptr)
{
return false;
}
for (const FGameplayAbilitySpec& Spec : AbilitySystem->GetActivatableAbilities())
{
if (Spec.Ability == nullptr)
{
continue;
}
if (SourceObject && Spec.SourceObject != SourceObject)
{
continue;
}
if (Spec.Ability->GetClass() == AbilityClass)
{
OutAbilityHandle = Spec.Handle;
return true;
}
}
return false;
}
bool UGGA_AbilitySystemFunctionLibrary::FindAbilityWithTags(const UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle& OutAbilityHandle, FGameplayTagContainer Tags, bool bExactMatch,
const UObject* SourceObject)
{
if (!IsValid(AbilitySystem) || Tags.IsEmpty())
{
return false;
}
// iterate through all Ability Specs
for (const FGameplayAbilitySpec& CurrentSpec : AbilitySystem->GetActivatableAbilities())
{
// try to get the ability instance
UGameplayAbility* AbilityInstance = CurrentSpec.GetPrimaryInstance();
// default to the CDO if we can't
if (!AbilityInstance)
{
AbilityInstance = CurrentSpec.Ability;
}
if (SourceObject && CurrentSpec.SourceObject != SourceObject)
{
continue;
}
// ensure the ability instance is valid
if (IsValid(AbilityInstance))
{
// do we want an exact match?
if (bExactMatch)
{
// check if we match all tags
if (AbilityInstance->GetAssetTags().HasAll(Tags))
{
OutAbilityHandle = CurrentSpec.Handle;
return true;
}
}
else
{
// check if we match any tags
if (AbilityInstance->GetAssetTags().HasAny(Tags))
{
OutAbilityHandle = CurrentSpec.Handle;
return true;
}
}
}
}
return false;
}
void UGGA_AbilitySystemFunctionLibrary::AddLooseGameplayTag(UAbilitySystemComponent* AbilitySystem, const FGameplayTag& GameplayTag, int32 Count)
{
if (!IsValid(AbilitySystem) || !GameplayTag.IsValid())
{
return;
}
AbilitySystem->AddLooseGameplayTag(GameplayTag, Count);
}
void UGGA_AbilitySystemFunctionLibrary::AddLooseGameplayTags(UAbilitySystemComponent* AbilitySystem, const FGameplayTagContainer& GameplayTags, int32 Count)
{
if (!IsValid(AbilitySystem) || GameplayTags.IsEmpty())
{
return;
}
AbilitySystem->AddLooseGameplayTags(GameplayTags, Count);
}
void UGGA_AbilitySystemFunctionLibrary::RemoveLooseGameplayTag(UAbilitySystemComponent* AbilitySystem, const FGameplayTag& GameplayTag, int32 Count)
{
if (!IsValid(AbilitySystem) || !GameplayTag.IsValid())
{
return;
}
AbilitySystem->RemoveLooseGameplayTag(GameplayTag, Count);
}
void UGGA_AbilitySystemFunctionLibrary::RemoveLooseGameplayTags(UAbilitySystemComponent* AbilitySystem, const FGameplayTagContainer& GameplayTags, int32 Count)
{
if (!IsValid(AbilitySystem) || GameplayTags.IsEmpty())
{
return;
}
AbilitySystem->RemoveLooseGameplayTags(GameplayTags, Count);
}
void UGGA_AbilitySystemFunctionLibrary::RemoveAllGameplayCues(UAbilitySystemComponent* AbilitySystem)
{
if (!IsValid(AbilitySystem))
{
return;
}
AbilitySystem->RemoveAllGameplayCues();
}
float UGGA_AbilitySystemFunctionLibrary::PlayMontage(UAbilitySystemComponent* AbilitySystem, UGameplayAbility* AnimatingAbility, FGameplayAbilityActivationInfo ActivationInfo, UAnimMontage* Montage,
float InPlayRate, FName StartSectionName,
float StartTimeSeconds)
{
if (!IsValid(AbilitySystem) || !Montage || !AnimatingAbility)
{
return -1.f;;
}
return AbilitySystem->PlayMontage(AnimatingAbility, ActivationInfo, Montage, InPlayRate, StartSectionName, StartTimeSeconds);
}
void UGGA_AbilitySystemFunctionLibrary::CurrentMontageStop(UAbilitySystemComponent* AbilitySystem, float OverrideBlendOutTime)
{
if (!IsValid(AbilitySystem))
{
return;
}
AbilitySystem->CurrentMontageStop(OverrideBlendOutTime);
}
void UGGA_AbilitySystemFunctionLibrary::StopMontageIfCurrent(UAbilitySystemComponent* AbilitySystem, const UAnimMontage* Montage, float OverrideBlendOutTime)
{
if (!IsValid(AbilitySystem) || !Montage)
{
return;
}
AbilitySystem->StopMontageIfCurrent(*Montage, OverrideBlendOutTime);
}
void UGGA_AbilitySystemFunctionLibrary::ClearAnimatingAbility(UAbilitySystemComponent* AbilitySystem, UGameplayAbility* Ability)
{
if (!IsValid(AbilitySystem) || !Ability)
{
return;
}
AbilitySystem->ClearAnimatingAbility(Ability);
}
void UGGA_AbilitySystemFunctionLibrary::CurrentMontageJumpToSection(UAbilitySystemComponent* AbilitySystem, FName SectionName)
{
if (!IsValid(AbilitySystem) || SectionName == NAME_None)
{
return;
}
AbilitySystem->CurrentMontageJumpToSection(SectionName);
}
void UGGA_AbilitySystemFunctionLibrary::CurrentMontageSetNextSectionName(UAbilitySystemComponent* AbilitySystem, FName FromSectionName, FName ToSectionName)
{
if (!IsValid(AbilitySystem) || FromSectionName == NAME_None || ToSectionName == NAME_None)
return;
AbilitySystem->CurrentMontageSetNextSectionName(FromSectionName, ToSectionName);
}
void UGGA_AbilitySystemFunctionLibrary::CurrentMontageSetPlayRate(UAbilitySystemComponent* AbilitySystem, float InPlayRate)
{
if (!IsValid(AbilitySystem) || InPlayRate <= 0.f)
{
return;
}
AbilitySystem->CurrentMontageSetPlayRate(InPlayRate);
}
UGameplayAbility* UGGA_AbilitySystemFunctionLibrary::GetAnimatingAbility(UAbilitySystemComponent* AbilitySystem)
{
if (!IsValid(AbilitySystem))
{
return nullptr;
}
return AbilitySystem->GetAnimatingAbility();
}
bool UGGA_AbilitySystemFunctionLibrary::IsAnimatingAbility(UAbilitySystemComponent* AbilitySystem, UGameplayAbility* Ability)
{
if (!IsValid(AbilitySystem))
{
return false;
}
return AbilitySystem->IsAnimatingAbility(Ability);
}
UGameplayAbility* UGGA_AbilitySystemFunctionLibrary::GetAnimatingAbilityFromActor(AActor* Actor, UAnimSequenceBase* Animation)
{
if (UAbilitySystemComponent* ASC = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(Actor))
{
if (ASC->GetCurrentMontage() == Animation)
{
if (UGameplayAbility* Ability = ASC->GetAnimatingAbility())
{
return Ability;
}
}
}
return nullptr;
}
bool UGGA_AbilitySystemFunctionLibrary::FindAnimatingAbilityFromActor(AActor* Actor, UAnimSequenceBase* Animation, TSubclassOf<UGameplayAbility> DesiredClass, UGameplayAbility*& AbilityInstance)
{
UGameplayAbility* Ability = GetAnimatingAbilityFromActor(Actor, Animation);
if (UClass* ActualClass = DesiredClass)
{
if (Ability && Ability->GetClass()->IsChildOf(ActualClass))
{
AbilityInstance = Ability;
return true;
}
}
return false;
}
UAnimMontage* UGGA_AbilitySystemFunctionLibrary::GetCurrentMontage(UAbilitySystemComponent* AbilitySystem)
{
if (!IsValid(AbilitySystem))
{
return nullptr;
}
return AbilitySystem->GetCurrentMontage();
}
int32 UGGA_AbilitySystemFunctionLibrary::GetCurrentMontageSectionID(UAbilitySystemComponent* AbilitySystem)
{
if (!IsValid(AbilitySystem))
{
return INDEX_NONE;
}
return AbilitySystem->GetCurrentMontageSectionID();
}
FName UGGA_AbilitySystemFunctionLibrary::GetCurrentMontageSectionName(UAbilitySystemComponent* AbilitySystem)
{
if (!IsValid(AbilitySystem))
{
return NAME_None;
}
return AbilitySystem->GetCurrentMontageSectionName();
}
float UGGA_AbilitySystemFunctionLibrary::GetCurrentMontageSectionLength(UAbilitySystemComponent* AbilitySystem)
{
if (!IsValid(AbilitySystem))
{
return 0.0f;
}
return AbilitySystem->GetCurrentMontageSectionLength();
}
float UGGA_AbilitySystemFunctionLibrary::GetCurrentMontageSectionTimeLeft(UAbilitySystemComponent* AbilitySystem)
{
if (!IsValid(AbilitySystem))
{
return 0.0f;
}
return AbilitySystem->GetCurrentMontageSectionTimeLeft();
}
void UGGA_AbilitySystemFunctionLibrary::SetAbilityInputPressed(UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle Ability)
{
if (!IsValid(AbilitySystem) || !Ability.IsValid())
{
return;
}
FScopedAbilityListLock ActiveScopeLock(*AbilitySystem);
if (FGameplayAbilitySpec* Spec = AbilitySystem->FindAbilitySpecFromHandle(Ability))
{
if (Spec->Ability)
{
Spec->InputPressed = true;
if (Spec->IsActive())
{
if (Spec->Ability->bReplicateInputDirectly && AbilitySystem->IsOwnerActorAuthoritative() == false)
{
AbilitySystem->ServerSetInputPressed(Spec->Handle);
}
AbilitySystem->AbilitySpecInputPressed(*Spec);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
#if ENGINE_MINOR_VERSION > 4
// Fixing this up to use the instance activation, but this function should be deprecated as it cannot work with InstancedPerExecution
UE_CLOG(Spec->Ability->GetInstancingPolicy() == EGameplayAbilityInstancingPolicy::InstancedPerExecution, LogAbilitySystem, Warning,
TEXT("%hs: %s is InstancedPerExecution. This is unreliable for Input as you may only interact with the latest spawned Instance"), __func__, *GetNameSafe(Spec->Ability));
TArray<UGameplayAbility*> Instances = Spec->GetAbilityInstances();
const FGameplayAbilityActivationInfo& ActivationInfo = Instances.IsEmpty() ? Spec->ActivationInfo : Instances.Last()->GetCurrentActivationInfoRef();
// Invoke the InputPressed event. This is not replicated here. If someone is listening, they may replicate the InputPressed event to the server.
AbilitySystem->InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputPressed, Spec->Handle, ActivationInfo.GetActivationPredictionKey());
#else
// Invoke the InputPressed event. This is not replicated here. If someone is listening, they may replicate the InputPressed event to the server.
AbilitySystem->InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputPressed, Spec->Handle, Spec->ActivationInfo.GetActivationPredictionKey());
#endif
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
else
{
// Ability is not active, so try to activate it
AbilitySystem->TryActivateAbility(Spec->Handle);
}
}
}
}
void UGGA_AbilitySystemFunctionLibrary::SetAbilityInputReleased(UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle Ability)
{
if (!IsValid(AbilitySystem) || !Ability.IsValid())
{
return;
}
FScopedAbilityListLock ActiveScopeLock(*AbilitySystem);
if (FGameplayAbilitySpec* Spec = AbilitySystem->FindAbilitySpecFromHandle(Ability))
{
Spec->InputPressed = false;
if (Spec->Ability && Spec->IsActive())
{
if (Spec->Ability->bReplicateInputDirectly && AbilitySystem->IsOwnerActorAuthoritative() == false)
{
AbilitySystem->ServerSetInputReleased(Spec->Handle);
}
AbilitySystem->AbilitySpecInputReleased(*Spec);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
#if ENGINE_MINOR_VERSION > 4
// Fixing this up to use the instance activation, but this function should be deprecated as it cannot work with InstancedPerExecution
UE_CLOG(Spec->Ability->GetInstancingPolicy() == EGameplayAbilityInstancingPolicy::InstancedPerExecution, LogAbilitySystem, Warning,
TEXT("%hs: %s is InstancedPerExecution. This is unreliable for Input as you may only interact with the latest spawned Instance"), __func__, *GetNameSafe(Spec->Ability));
TArray<UGameplayAbility*> Instances = Spec->GetAbilityInstances();
const FGameplayAbilityActivationInfo& ActivationInfo = Instances.IsEmpty() ? Spec->ActivationInfo : Instances.Last()->GetCurrentActivationInfoRef();
AbilitySystem->InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputReleased, Spec->Handle, ActivationInfo.GetActivationPredictionKey());
#else
AbilitySystem->InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputReleased, Spec->Handle, Spec->ActivationInfo.GetActivationPredictionKey());
#endif
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
}
}
bool UGGA_AbilitySystemFunctionLibrary::CanActivateAbility(const UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle AbilityToActivate, FGameplayTagContainer& RelevantTags)
{
if (!IsValid(AbilitySystem) || !AbilityToActivate.IsValid())
{
return false;
}
FGameplayTagContainer FailureTags;
FGameplayAbilitySpec* Spec = AbilitySystem->FindAbilitySpecFromHandle(AbilityToActivate);
if (!Spec)
{
ABILITY_LOG(Warning, TEXT("CanActivateAbility called with invalid Handle"));
return false;
}
// Lock ability list so our Spec doesn't get destroyed while activating
const FGameplayAbilityActorInfo* ActorInfo = AbilitySystem->AbilityActorInfo.Get();
UGameplayAbility* AbilityCDO = Spec->Ability;
if (!AbilityCDO)
{
ABILITY_LOG(Warning, TEXT("CanActivateAbility called with ability with invalid definition"));
return false;
}
// If it's instance once the instanced ability will be set, otherwise it will be null
UGameplayAbility* InstancedAbility = Spec->GetPrimaryInstance();
// If we have an instanced ability, call CanActivateAbility on it.
// Otherwise we always do a non instanced CanActivateAbility check using the CDO of the Ability.
UGameplayAbility* const CanActivateAbilitySource = InstancedAbility ? InstancedAbility : AbilityCDO;
return CanActivateAbilitySource->CanActivateAbility(AbilityToActivate, ActorInfo, nullptr, nullptr, &RelevantTags);
}
bool UGGA_AbilitySystemFunctionLibrary::SelectFirstCanActivateAbility(const UAbilitySystemComponent* AbilitySystem, TArray<FGameplayAbilitySpecHandle> Abilities,
FGameplayAbilitySpecHandle& OutAbilityHandle)
{
for (int32 i = 0; i < Abilities.Num(); i++)
{
static FGameplayTagContainer DummyTags;
DummyTags.Reset();
if (CanActivateAbility(AbilitySystem, Abilities[i], DummyTags))
{
OutAbilityHandle = Abilities[i];
return true;
}
}
return false;
}
void UGGA_AbilitySystemFunctionLibrary::CancelAbility(UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle Ability)
{
if (!IsValid(AbilitySystem) || !Ability.IsValid())
{
return;
}
AbilitySystem->CancelAbilityHandle(Ability);
}
void UGGA_AbilitySystemFunctionLibrary::CancelAbilities(UAbilitySystemComponent* AbilitySystem, FGameplayTagContainer WithTags, FGameplayTagContainer WithoutTags)
{
if (!IsValid(AbilitySystem))
{
return;
}
AbilitySystem->CancelAbilities(&WithTags, &WithoutTags);
}
bool UGGA_AbilitySystemFunctionLibrary::IsAbilityInstanceActive(const UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle Ability)
{
if (!IsValid(AbilitySystem) || !Ability.IsValid())
{
return false;
}
if (FGameplayAbilitySpec* AbilitySpec = AbilitySystem->FindAbilitySpecFromHandle(Ability))
{
if (UGameplayAbility* AbilityInstance = AbilitySpec->GetPrimaryInstance())
{
return AbilityInstance->IsActive();
}
}
return false;
}
bool UGGA_AbilitySystemFunctionLibrary::IsAbilityActive(const UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle Ability)
{
if (!IsValid(AbilitySystem) || !Ability.IsValid())
{
return false;
}
if (FGameplayAbilitySpec* AbilitySpec = AbilitySystem->FindAbilitySpecFromHandle(Ability))
{
return AbilitySpec->IsActive();
}
return false;
}
TArray<UGameplayAbility*> UGGA_AbilitySystemFunctionLibrary::GetAbilityInstances(UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle Ability)
{
TArray<UGameplayAbility*> Abilities;
if (!IsValid(AbilitySystem) || !Ability.IsValid())
{
return Abilities;
}
if (FGameplayAbilitySpec* AbilitySpec = AbilitySystem->FindAbilitySpecFromHandle(Ability))
{
Abilities = AbilitySpec->GetAbilityInstances();
return Abilities;
}
return Abilities;
}
const UGameplayAbility* UGGA_AbilitySystemFunctionLibrary::GetAbilityCDO(UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle Ability)
{
if (!IsValid(AbilitySystem) || !Ability.IsValid())
{
return nullptr;
}
if (FGameplayAbilitySpec* AbilitySpec = AbilitySystem->FindAbilitySpecFromHandle(Ability))
{
return AbilitySpec->Ability;
}
return nullptr;
}
int32 UGGA_AbilitySystemFunctionLibrary::GetAbilityLevel(UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle Ability)
{
if (!IsValid(AbilitySystem) || !Ability.IsValid())
{
return 0;
}
if (FGameplayAbilitySpec* AbilitySpec = AbilitySystem->FindAbilitySpecFromHandle(Ability))
{
return AbilitySpec->Level;
}
return 0;
}
int32 UGGA_AbilitySystemFunctionLibrary::GetAbilityInputId(UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle Ability)
{
if (!IsValid(AbilitySystem) || !Ability.IsValid())
{
return -1;
}
if (FGameplayAbilitySpec* AbilitySpec = AbilitySystem->FindAbilitySpecFromHandle(Ability))
{
return AbilitySpec->InputID;
}
return -1;
}
UObject* UGGA_AbilitySystemFunctionLibrary::GetAbilitySourceObject(UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle Ability)
{
if (!IsValid(AbilitySystem) || !Ability.IsValid())
{
return nullptr;
}
if (FGameplayAbilitySpec* AbilitySpec = AbilitySystem->FindAbilitySpecFromHandle(Ability))
{
if (AbilitySpec->SourceObject.IsValid())
{
return AbilitySpec->SourceObject.Get();
}
}
return nullptr;
}
FGameplayTagContainer UGGA_AbilitySystemFunctionLibrary::GetAbilityDynamicTags(UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle Ability)
{
if (!IsValid(AbilitySystem) || !Ability.IsValid())
{
return FGameplayTagContainer::EmptyContainer;
}
if (FGameplayAbilitySpec* AbilitySpec = AbilitySystem->FindAbilitySpecFromHandle(Ability))
{
#if ENGINE_MINOR_VERSION > 4
return AbilitySpec->GetDynamicSpecSourceTags();
#else
return AbilitySpec->DynamicAbilityTags;
#endif
}
return FGameplayTagContainer::EmptyContainer;
}
UGameplayAbility* UGGA_AbilitySystemFunctionLibrary::GetAbilityPrimaryInstance(UAbilitySystemComponent* AbilitySystem, FGameplayAbilitySpecHandle Ability)
{
if (!IsValid(AbilitySystem) || !Ability.IsValid())
{
return nullptr;
}
if (FGameplayAbilitySpec* AbilitySpec = AbilitySystem->FindAbilitySpecFromHandle(Ability))
{
return AbilitySpec->GetPrimaryInstance();
}
return nullptr;
}
UAttributeSet* UGGA_AbilitySystemFunctionLibrary::GetAttributeSetByClass(const UAbilitySystemComponent* AbilitySystem, const TSubclassOf<UAttributeSet> AttributeSetClass)
{
if (!IsValid(AbilitySystem) || !AttributeSetClass)
{
return nullptr;
}
for (UAttributeSet* SpawnedAttribute : AbilitySystem->GetSpawnedAttributes())
{
if (SpawnedAttribute && SpawnedAttribute->IsA(AttributeSetClass))
{
return SpawnedAttribute;
}
}
return nullptr;
}
float UGGA_AbilitySystemFunctionLibrary::GetValueAtLevel(const FScalableFloat& ScalableFloat, float Level, FString ContextString)
{
return ScalableFloat.GetValueAtLevel(Level, &ContextString);
}

View File

@@ -0,0 +1,71 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Utilities/GGA_GameplayAbilityFunctionLibrary.h"
#include "Runtime/Launch/Resources/Version.h"
#include "Abilities/GameplayAbility.h"
bool UGGA_GameplayAbilityFunctionLibrary::IsAbilitySpecHandleValid(FGameplayAbilitySpecHandle Handle)
{
return Handle.IsValid();
}
const UGameplayAbility* UGGA_GameplayAbilityFunctionLibrary::GetAbilityCDOFromClass(TSubclassOf<UGameplayAbility> AbilityClass)
{
if (IsValid(AbilityClass))
{
return AbilityClass->GetDefaultObject<UGameplayAbility>();
}
return nullptr;
}
FGameplayAbilitySpecHandle UGGA_GameplayAbilityFunctionLibrary::GetCurrentAbilitySpecHandle(const UGameplayAbility* Ability)
{
return IsValid(Ability) ? Ability->GetCurrentAbilitySpecHandle() : FGameplayAbilitySpecHandle();
}
bool UGGA_GameplayAbilityFunctionLibrary::IsAbilityActive(const UGameplayAbility* Ability)
{
return IsValid(Ability) ? Ability->IsActive() : false;
}
EGameplayAbilityReplicationPolicy::Type UGGA_GameplayAbilityFunctionLibrary::GetReplicationPolicy(const UGameplayAbility* Ability)
{
return IsValid(Ability) ? Ability->GetReplicationPolicy() : EGameplayAbilityReplicationPolicy::ReplicateNo;
}
EGameplayAbilityInstancingPolicy::Type UGGA_GameplayAbilityFunctionLibrary::GetInstancingPolicy(const UGameplayAbility* Ability)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
return IsValid(Ability) ? Ability->GetInstancingPolicy() : EGameplayAbilityInstancingPolicy::NonInstanced;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
FGameplayTagContainer UGGA_GameplayAbilityFunctionLibrary::GetAbilityTags(const UGameplayAbility* Ability)
{
#if ENGINE_MINOR_VERSION > 4
return IsValid(Ability) ? Ability->GetAssetTags() : FGameplayTagContainer::EmptyContainer;
#else
return IsValid(Ability) ? Ability->AbilityTags : FGameplayTagContainer::EmptyContainer;
#endif
}
bool UGGA_GameplayAbilityFunctionLibrary::IsPredictingClient(const UGameplayAbility* Ability)
{
return IsValid(Ability) ? Ability->IsPredictingClient() : false;
}
bool UGGA_GameplayAbilityFunctionLibrary::IsForRemoteClient(const UGameplayAbility* Ability)
{
return IsValid(Ability) ? Ability->IsForRemoteClient() : false;
}
bool UGGA_GameplayAbilityFunctionLibrary::HasAuthorityOrPredictionKey(const UGameplayAbility* Ability)
{
if (IsValid(Ability))
{
const FGameplayAbilityActivationInfo& ActivationInfo = Ability->GetCurrentActivationInfo();
return Ability->HasAuthorityOrPredictionKey(Ability->GetCurrentActorInfo(), &ActivationInfo);
}
return IsValid(Ability) ? Ability->IsForRemoteClient() : false;
}

View File

@@ -0,0 +1,75 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Utilities/GGA_GameplayAbilityTargetDataFunctionLibrary.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "GGA_GameplayAbilityTargetData_Payload.h"
FGameplayAbilityTargetDataHandle UGGA_GameplayAbilityTargetDataFunctionLibrary::AbilityTargetDataFromPayload(const FInstancedStruct& Payload)
{
// Construct TargetData
FGGA_GameplayAbilityTargetData_Payload* TargetData = new FGGA_GameplayAbilityTargetData_Payload(Payload);
// Give it a handle and return
FGameplayAbilityTargetDataHandle Handle;
Handle.Data.Add(TSharedPtr<FGameplayAbilityTargetData>(TargetData));
return Handle;
}
FInstancedStruct UGGA_GameplayAbilityTargetDataFunctionLibrary::GetPayloadFromTargetData(const FGameplayAbilityTargetDataHandle& TargetData, int32 Index)
{
if (TargetData.Data.IsValidIndex(Index))
{
if (TargetData.Data[Index]->GetScriptStruct() == FGGA_GameplayAbilityTargetData_Payload::StaticStruct())
{
if (FGGA_GameplayAbilityTargetData_Payload* Data = static_cast<FGGA_GameplayAbilityTargetData_Payload*>(TargetData.Data[Index].Get()))
{
return Data->Payload;
}
}
}
return FInstancedStruct();
}
FGameplayAbilityTargetDataHandle UGGA_GameplayAbilityTargetDataFunctionLibrary::AbilityTargetDataFromHitResults(const TArray<FHitResult>& HitResults, bool OneTargetPerHandle)
{
// Construct TargetData
if (OneTargetPerHandle)
{
FGameplayAbilityTargetDataHandle Handle;
for (int32 i = 0; i < HitResults.Num(); ++i)
{
if (::IsValid(HitResults[i].GetActor()))
{
FGameplayAbilityTargetDataHandle TempHandle = UAbilitySystemBlueprintLibrary::AbilityTargetDataFromHitResult(HitResults[i]);
Handle.Append(TempHandle);
}
}
return Handle;
}
else
{
FGameplayAbilityTargetDataHandle Handle;
for (const FHitResult& HitResult : HitResults)
{
FGameplayAbilityTargetData_SingleTargetHit* NewData = new FGameplayAbilityTargetData_SingleTargetHit(HitResult);
Handle.Add(NewData);
}
return Handle;
}
}
void UGGA_GameplayAbilityTargetDataFunctionLibrary::AddTargetDataToContext(FGameplayAbilityTargetDataHandle TargetData, FGameplayEffectContextHandle EffectContext)
{
for (auto Data : TargetData.Data)
{
if (Data.IsValid())
{
Data->AddTargetDataToContext(EffectContext, true);
}
}
}

View File

@@ -0,0 +1,27 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Utilities/GGA_GameplayCueFunctionLibrary.h"
#include "AbilitySystemGlobals.h"
#include "GameplayCueManager.h"
void UGGA_GameplayCueFunctionLibrary::ExecuteGameplayCueLocal(AActor* Actor, const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters)
{
UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(
Actor, GameplayCueTag, EGameplayCueEvent::Type::Executed,
GameplayCueParameters);
}
void UGGA_GameplayCueFunctionLibrary::AddGameplayCueLocal(AActor* Actor, const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters)
{
UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(Actor, GameplayCueTag, EGameplayCueEvent::Type::OnActive, GameplayCueParameters);
UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(Actor, GameplayCueTag, EGameplayCueEvent::Type::WhileActive, GameplayCueParameters);
}
void UGGA_GameplayCueFunctionLibrary::RemoveGameplayCueLocal(AActor* Actor, const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters)
{
UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(
Actor, GameplayCueTag, EGameplayCueEvent::Type::Removed,
GameplayCueParameters);
}

View File

@@ -0,0 +1,172 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Utilities/GGA_GameplayEffectCalculationFunctionLibrary.h"
#include "AbilitySystemComponent.h"
const FGameplayEffectSpec& UGGA_GameplayEffectCalculationFunctionLibrary::GetOwningSpec(const FGameplayEffectCustomExecutionParameters& InParams)
{
return InParams.GetOwningSpec();
}
const FGameplayTagContainer& UGGA_GameplayEffectCalculationFunctionLibrary::GetPassedInTags(const FGameplayEffectCustomExecutionParameters& InParams)
{
return InParams.GetPassedInTags();
}
FGameplayEffectContextHandle UGGA_GameplayEffectCalculationFunctionLibrary::GetEffectContext(const FGameplayEffectCustomExecutionParameters& InParams)
{
return InParams.GetOwningSpec().GetEffectContext();
}
float UGGA_GameplayEffectCalculationFunctionLibrary::GetSetByCallerMagnitudeByTag(const FGameplayEffectCustomExecutionParameters& InParams, const FGameplayTag& Tag, bool WarnIfNotFound,
float DefaultIfNotFound)
{
return InParams.GetOwningSpec().GetSetByCallerMagnitude(Tag, WarnIfNotFound, DefaultIfNotFound);
}
float UGGA_GameplayEffectCalculationFunctionLibrary::GetSetByCallerMagnitudeByName(const FGameplayEffectCustomExecutionParameters& InParams, const FName& MagnitudeName, bool WarnIfNotFound,
float DefaultIfNotFound)
{
return InParams.GetOwningSpec().GetSetByCallerMagnitude(MagnitudeName, WarnIfNotFound, DefaultIfNotFound);
}
FGameplayTagContainer UGGA_GameplayEffectCalculationFunctionLibrary::GetSourceAggregatedTags(const FGameplayEffectCustomExecutionParameters& InParams)
{
return *InParams.GetOwningSpec().CapturedSourceTags.GetAggregatedTags();
}
FGameplayTagContainer UGGA_GameplayEffectCalculationFunctionLibrary::GetTargetAggregatedTags(const FGameplayEffectCustomExecutionParameters& InParams)
{
return *InParams.GetOwningSpec().CapturedTargetTags.GetAggregatedTags();
}
UAbilitySystemComponent* UGGA_GameplayEffectCalculationFunctionLibrary::GetTargetASC(const FGameplayEffectCustomExecutionParameters& InParams)
{
return InParams.GetTargetAbilitySystemComponent();
}
AActor* UGGA_GameplayEffectCalculationFunctionLibrary::GetTargetActor(const FGameplayEffectCustomExecutionParameters& InParams)
{
return InParams.GetTargetAbilitySystemComponent()->GetAvatarActor();
}
UAbilitySystemComponent* UGGA_GameplayEffectCalculationFunctionLibrary::GetSourceASC(const FGameplayEffectCustomExecutionParameters& InParams)
{
return InParams.GetSourceAbilitySystemComponent();
}
AActor* UGGA_GameplayEffectCalculationFunctionLibrary::GetSourceActor(const FGameplayEffectCustomExecutionParameters& InParams)
{
return InParams.GetSourceAbilitySystemComponent()->GetAvatarActor();
}
bool UGGA_GameplayEffectCalculationFunctionLibrary::AttemptCalculateCapturedAttributeMagnitude(const FGameplayEffectCustomExecutionParameters& InParams,
TArray<FGameplayEffectAttributeCaptureDefinition> InAttributeCaptureDefinitions,
FGameplayAttribute InAttribute, float& OutMagnitude)
{
FAggregatorEvaluateParameters EvaluationParams;
const FGameplayEffectSpec& EffectSpec = InParams.GetOwningSpec();
EvaluationParams.SourceTags = EffectSpec.CapturedSourceTags.GetAggregatedTags();
EvaluationParams.TargetTags = EffectSpec.CapturedTargetTags.GetAggregatedTags();
for (const FGameplayEffectAttributeCaptureDefinition& AttributeCaptureDefinition : InAttributeCaptureDefinitions)
{
if (AttributeCaptureDefinition.AttributeToCapture == InAttribute)
{
return InParams.AttemptCalculateCapturedAttributeMagnitude(AttributeCaptureDefinition, EvaluationParams, OutMagnitude);
}
}
return false;
}
bool UGGA_GameplayEffectCalculationFunctionLibrary::AttemptCalculateCapturedAttributeMagnitudeExt(const FGameplayEffectCustomExecutionParameters& InParams, const FGameplayTagContainer& SourceTags,
const FGameplayTagContainer& TargetTags,
TArray<FGameplayEffectAttributeCaptureDefinition> InAttributeCaptureDefinitions,
FGameplayAttribute InAttribute, float& OutMagnitude)
{
FAggregatorEvaluateParameters EvaluationParams;
EvaluationParams.SourceTags = &SourceTags;
EvaluationParams.TargetTags = &TargetTags;
for (const FGameplayEffectAttributeCaptureDefinition& AttributeCaptureDefinition : InAttributeCaptureDefinitions)
{
if (AttributeCaptureDefinition.AttributeToCapture == InAttribute)
{
return InParams.AttemptCalculateCapturedAttributeMagnitude(AttributeCaptureDefinition, EvaluationParams, OutMagnitude);
}
}
return false;
}
bool UGGA_GameplayEffectCalculationFunctionLibrary::AttemptCalculateCapturedAttributeMagnitudeWithBase(const FGameplayEffectCustomExecutionParameters& InParams,
TArray<FGameplayEffectAttributeCaptureDefinition> InAttributeCaptureDefinitions,
FGameplayAttribute InAttribute, float InBaseValue, float& OutMagnitude)
{
FAggregatorEvaluateParameters EvaluationParams;
const FGameplayEffectSpec& EffectSpec = InParams.GetOwningSpec();
EvaluationParams.SourceTags = EffectSpec.CapturedSourceTags.GetAggregatedTags();
EvaluationParams.TargetTags = EffectSpec.CapturedTargetTags.GetAggregatedTags();
for (const FGameplayEffectAttributeCaptureDefinition& AttributeCaptureDefinition : InAttributeCaptureDefinitions)
{
if (AttributeCaptureDefinition.AttributeToCapture == InAttribute)
{
return InParams.AttemptCalculateCapturedAttributeMagnitudeWithBase(AttributeCaptureDefinition, EvaluationParams, InBaseValue, OutMagnitude);
}
}
return false;
}
FGameplayEffectCustomExecutionOutput UGGA_GameplayEffectCalculationFunctionLibrary::AddOutputModifier(FGameplayEffectCustomExecutionOutput& InExecutionOutput, FGameplayAttribute InAttribute,
EGameplayModOp::Type InModifierOp, float InMagnitude)
{
if (InAttribute.IsValid())
{
FGameplayModifierEvaluatedData Data;
Data.Attribute = InAttribute;
Data.ModifierOp = InModifierOp;
Data.Magnitude = InMagnitude;
InExecutionOutput.AddOutputModifier(Data);
}
return InExecutionOutput;
}
void UGGA_GameplayEffectCalculationFunctionLibrary::MarkConditionalGameplayEffectsToTrigger(FGameplayEffectCustomExecutionOutput& InExecutionOutput)
{
InExecutionOutput.MarkConditionalGameplayEffectsToTrigger();
}
void UGGA_GameplayEffectCalculationFunctionLibrary::MarkGameplayCuesHandledManually(FGameplayEffectCustomExecutionOutput& InExecutionOutput)
{
InExecutionOutput.MarkGameplayCuesHandledManually();
}
void UGGA_GameplayEffectCalculationFunctionLibrary::MarkStackCountHandledManually(FGameplayEffectCustomExecutionOutput& InExecutionOutput)
{
InExecutionOutput.MarkStackCountHandledManually();
}
FGameplayEffectContextHandle UGGA_GameplayEffectCalculationFunctionLibrary::GetEffectContextFromSpec(const FGameplayEffectSpec& EffectSpec)
{
return EffectSpec.GetEffectContext();
}
void UGGA_GameplayEffectCalculationFunctionLibrary::AddAssetTagForPreMod(const FGameplayEffectCustomExecutionParameters& InParams, FGameplayTag NewGameplayTag)
{
if (FGameplayEffectSpec* Spec = InParams.GetOwningSpecForPreExecuteMod())
{
Spec->AddDynamicAssetTag(NewGameplayTag);
}
}
void UGGA_GameplayEffectCalculationFunctionLibrary::AddAssetTagsForPreMod(const FGameplayEffectCustomExecutionParameters& InParams, FGameplayTagContainer NewGameplayTags)
{
if (FGameplayEffectSpec* Spec = InParams.GetOwningSpecForPreExecuteMod())
{
Spec->AppendDynamicAssetTags(NewGameplayTags);
}
}

View File

@@ -0,0 +1,158 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Utilities/GGA_GameplayEffectContainerFunctionLibrary.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemComponent.h"
#include "GameplayAbilitySpec.h"
#include "AbilitySystemGlobals.h"
#include "GGA_LogChannels.h"
#include "TargetingSystem/TargetingSubsystem.h"
bool UGGA_GameplayEffectContainerFunctionLibrary::IsValidContainer(const FGGA_GameplayEffectContainer& Container)
{
return !Container.TargetGameplayEffectClasses.IsEmpty() && Container.TargetingPreset != nullptr;
}
bool UGGA_GameplayEffectContainerFunctionLibrary::HasValidEffects(const FGGA_GameplayEffectContainerSpec& ContainerSpec)
{
return ContainerSpec.HasValidEffects();
}
bool UGGA_GameplayEffectContainerFunctionLibrary::HasValidTargets(const FGGA_GameplayEffectContainerSpec& ContainerSpec)
{
return ContainerSpec.HasValidTargets();
}
FGGA_GameplayEffectContainerSpec UGGA_GameplayEffectContainerFunctionLibrary::AddTargets(const FGGA_GameplayEffectContainerSpec& ContainerSpec, const TArray<FHitResult>& HitResults,
const TArray<AActor*>& TargetActors)
{
FGGA_GameplayEffectContainerSpec NewSpec = ContainerSpec;
NewSpec.AddTargets(HitResults, TargetActors);
return NewSpec;
}
FGGA_GameplayEffectContainerSpec UGGA_GameplayEffectContainerFunctionLibrary::MakeEffectContainerSpec(const FGGA_GameplayEffectContainer& Container,
const FGameplayEventData& EventData, int32 OverrideGameplayLevel, UGameplayAbility* SourceAbility)
{
FGGA_GameplayEffectContainerSpec ReturnSpec;
if (SourceAbility== nullptr && EventData.Instigator == nullptr)
{
UE_LOG(LogGGA_AbilitySystem, Warning, TEXT("Missing instigator within EventData, It's required to look for Source AbilitySystemComponent!"))
return ReturnSpec;
}
// First figure out our actor info
UAbilitySystemComponent* SourceASC = SourceAbility?SourceAbility->GetAbilitySystemComponentFromActorInfo():UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(EventData.Instigator);
if (SourceASC && SourceASC->GetAvatarActor())
{
if (Container.TargetingPreset.Get())
{
if (UTargetingSubsystem* TargetingSubsystem = UTargetingSubsystem::Get(SourceASC->GetWorld()))
{
FTargetingSourceContext SourceContext;
SourceContext.SourceActor = SourceASC->GetAvatarActor();
FTargetingRequestHandle TargetingHandle = UTargetingSubsystem::MakeTargetRequestHandle(Container.TargetingPreset, SourceContext);
FTargetingRequestDelegate Delegate = FTargetingRequestDelegate::CreateLambda([&](FTargetingRequestHandle InTargetingHandle)
{
TArray<FHitResult> HitResults;
TArray<AActor*> TargetActors;
TargetingSubsystem->GetTargetingResults(InTargetingHandle, HitResults);
ReturnSpec.AddTargets(HitResults, TargetActors);
});
UE_LOG(LogGGA_AbilitySystem, VeryVerbose, TEXT("Starting immediate targeting task for EffectContainer."));
FTargetingImmediateTaskData& ImmediateTaskData = FTargetingImmediateTaskData::FindOrAdd(TargetingHandle);
ImmediateTaskData.bReleaseOnCompletion = true;
TargetingSubsystem->ExecuteTargetingRequestWithHandle(TargetingHandle, Delegate);
}
}
int32 Level = OverrideGameplayLevel >= 0 ? OverrideGameplayLevel : EventData.EventMagnitude;
// Build GameplayEffectSpecs for each applied effect
for (const TSubclassOf<UGameplayEffect>& EffectClass : Container.TargetGameplayEffectClasses)
{
FGameplayEffectSpecHandle SpecHandle = SourceAbility?SourceAbility->MakeOutgoingGameplayEffectSpec(EffectClass, Level):SourceASC->MakeOutgoingSpec(EffectClass, Level, SourceASC->MakeEffectContext());
FGameplayEffectContextHandle ContextHandle = UAbilitySystemBlueprintLibrary::GetEffectContext(SpecHandle);
if (SourceAbility==nullptr && EventData.OptionalObject)
{
ContextHandle.AddSourceObject(EventData.OptionalObject);
}
ReturnSpec.TargetGameplayEffectSpecs.Add(SpecHandle);
}
if (EventData.TargetData.Num() > 0)
{
ReturnSpec.TargetData.Append(EventData.TargetData);
}
}
return ReturnSpec;
}
TArray<FActiveGameplayEffectHandle> UGGA_GameplayEffectContainerFunctionLibrary::ApplyEffectContainerSpec(UGameplayAbility* ExecutingAbility, const FGGA_GameplayEffectContainerSpec& ContainerSpec)
{
TArray<FActiveGameplayEffectHandle> AllEffects;
if (!IsValid(ExecutingAbility) || !ExecutingAbility->IsInstantiated())
{
UE_LOG(LogGGA_Ability, Error, TEXT("Requires \"Executing ability\" to apply effect container spec."))
return AllEffects;
}
const FGameplayAbilityActorInfo* ActorInfo = ExecutingAbility->GetCurrentActorInfo();
const FGameplayAbilityActivationInfo& ActivationInfo = ExecutingAbility->GetCurrentActivationInfoRef();
// Iterate list of effect specs and apply them to their target data
for (const FGameplayEffectSpecHandle& SpecHandle : ContainerSpec.TargetGameplayEffectSpecs)
{
TArray<FActiveGameplayEffectHandle> EffectHandles;
if (SpecHandle.IsValid() && ExecutingAbility->HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo))
{
FScopedTargetListLock ActiveScopeLock(*ActorInfo->AbilitySystemComponent, *ExecutingAbility);
for (TSharedPtr<FGameplayAbilityTargetData> Data : ContainerSpec.TargetData.Data)
{
if (Data.IsValid())
{
AllEffects.Append(Data->ApplyGameplayEffectSpec(*SpecHandle.Data.Get(), ActorInfo->AbilitySystemComponent->GetPredictionKeyForNewAction()));
}
else
{
UE_LOG(LogGGA_Ability, Warning, TEXT("ApplyGameplayEffectSpecToTarget invalid target data passed in. Ability: %s"), *ExecutingAbility->GetPathName());
}
}
}
}
return AllEffects;
}
TArray<FActiveGameplayEffectHandle> UGGA_GameplayEffectContainerFunctionLibrary::ApplyExternalEffectContainerSpec(const FGGA_GameplayEffectContainerSpec& ContainerSpec)
{
TArray<FActiveGameplayEffectHandle> AllEffects;
// Iterate list of gameplay effects
for (const FGameplayEffectSpecHandle& SpecHandle : ContainerSpec.TargetGameplayEffectSpecs)
{
if (SpecHandle.IsValid())
{
// If effect is valid, iterate list of targets and apply to all
for (TSharedPtr<FGameplayAbilityTargetData> Data : ContainerSpec.TargetData.Data)
{
AllEffects.Append(Data->ApplyGameplayEffectSpec(*SpecHandle.Data.Get()));
}
}
}
return AllEffects;
}

View File

@@ -0,0 +1,284 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Utilities/GGA_GameplayEffectFunctionLibrary.h"
#include "GameplayEffect.h"
#include "Blueprint/BlueprintExceptionInfo.h"
#include "UObject/EnumProperty.h"
#include "GGA_GameplayEffectContext.h"
#include "GGA_LogChannels.h"
#define LOCTEXT_NAMESPACE "UGGA_GameplayEffectFunctionLibrary"
// UGameplayEffect* UGGA_GameplayEffectFunctionLibrary::MakeRuntimeGameplayEffect(FString UniqueName, EGameplayEffectDurationType DurationPolicy, TArray<FGameplayModifierInfo> AttributeModifiers)
// {
// UGameplayEffect* GameplayEffect = NewObject<UGameplayEffect>(GetTransientPackage(), FName("RuntimeGE_" + GetNameSafe(this) + UniqueName));
// GameplayEffect->DurationPolicy = DurationPolicy;
// GameplayEffect->Modifiers = AttributeModifiers;
// return GameplayEffect;
// }
float UGGA_GameplayEffectFunctionLibrary::GetSetByCallerMagnitudeByTag(FGameplayEffectSpecHandle SpecHandle, FGameplayTag DataTag, bool WarnIfNotFound, float DefaultIfNotFound)
{
FGameplayEffectSpec* Spec = SpecHandle.Data.Get();
if (Spec)
{
return Spec->GetSetByCallerMagnitude(DataTag, WarnIfNotFound, DefaultIfNotFound);
}
return 0.0f;
}
float UGGA_GameplayEffectFunctionLibrary::GetSetByCallerMagnitudeByTagFromSpec(const FGameplayEffectSpec& EffectSpec, FGameplayTag DataTag, bool WarnIfNotFound, float DefaultIfNotFound)
{
return EffectSpec.GetSetByCallerMagnitude(DataTag, WarnIfNotFound, DefaultIfNotFound);
}
float UGGA_GameplayEffectFunctionLibrary::GetSetByCallerMagnitudeByName(FGameplayEffectSpecHandle SpecHandle, FName DataName)
{
FGameplayEffectSpec* Spec = SpecHandle.Data.Get();
if (Spec)
{
return Spec->GetSetByCallerMagnitude(DataName, false);
}
return 0.0f;
}
bool UGGA_GameplayEffectFunctionLibrary::IsActiveGameplayEffectHandleValid(FActiveGameplayEffectHandle Handle)
{
return Handle.IsValid();
}
void UGGA_GameplayEffectFunctionLibrary::GetOwnedGameplayTags(FGameplayEffectContextHandle EffectContext, FGameplayTagContainer& ActorTagContainer, FGameplayTagContainer& SpecTagContainer)
{
return EffectContext.GetOwnedGameplayTags(ActorTagContainer, SpecTagContainer);
}
void UGGA_GameplayEffectFunctionLibrary::AddInstigator(FGameplayEffectContextHandle EffectContext, AActor* InInstigator, AActor* InEffectCauser)
{
EffectContext.AddInstigator(InInstigator, InEffectCauser);
}
void UGGA_GameplayEffectFunctionLibrary::SetEffectCauser(FGameplayEffectContextHandle EffectContext, AActor* InEffectCauser)
{
EffectContext.AddInstigator(EffectContext.GetInstigator(), InEffectCauser);
}
void UGGA_GameplayEffectFunctionLibrary::SetAbility(FGameplayEffectContextHandle EffectContext, const UGameplayAbility* InGameplayAbility)
{
EffectContext.SetAbility(InGameplayAbility);
}
const UGameplayAbility* UGGA_GameplayEffectFunctionLibrary::GetAbilityCDO(FGameplayEffectContextHandle EffectContext)
{
return EffectContext.GetAbility();
}
const UGameplayAbility* UGGA_GameplayEffectFunctionLibrary::GetAbilityInstance(FGameplayEffectContextHandle EffectContext)
{
return EffectContext.GetAbilityInstance_NotReplicated();
}
int32 UGGA_GameplayEffectFunctionLibrary::GetAbilityLevel(FGameplayEffectContextHandle EffectContext)
{
return EffectContext.GetAbilityLevel();
}
void UGGA_GameplayEffectFunctionLibrary::AddSourceObject(FGameplayEffectContextHandle EffectContext, const UObject* NewSourceObject)
{
if (NewSourceObject)
{
EffectContext.AddSourceObject(NewSourceObject);
}
}
bool UGGA_GameplayEffectFunctionLibrary::HasOrigin(FGameplayEffectContextHandle EffectContext)
{
return EffectContext.HasOrigin();
}
FGGA_GameplayEffectContext* UGGA_GameplayEffectFunctionLibrary::GetEffectContextPtr(FGameplayEffectContextHandle EffectContext)
{
if (!EffectContext.IsValid())
{
GGA_LOG(Warning, "Try access invalid effect context!")
return nullptr;
}
if (!EffectContext.Get()->GetScriptStruct()->IsChildOf(FGGA_GameplayEffectContext::StaticStruct()))
{
GGA_LOG(Warning, "The GameplayEffectContext type is not FGGA_GameplayEffectContext! "
"Make sure you are setting AbilitySystemGlobalsClassName as GGA_AbilitySystemGlobals in Gameplay Abilities Settings Under Project Settings! ")
return nullptr;
}
return static_cast<FGGA_GameplayEffectContext*>(EffectContext.Get());
}
UAbilitySystemComponent* UGGA_GameplayEffectFunctionLibrary::GetInstigatorAbilitySystemComponent(FGameplayEffectContextHandle EffectContext)
{
return EffectContext.GetInstigatorAbilitySystemComponent();
}
UAbilitySystemComponent* UGGA_GameplayEffectFunctionLibrary::GetOriginalInstigatorAbilitySystemComponent(FGameplayEffectContextHandle EffectContext)
{
return EffectContext.GetOriginalInstigatorAbilitySystemComponent();
}
bool UGGA_GameplayEffectFunctionLibrary::HasContextPayload(FGameplayEffectContextHandle EffectContext, const UScriptStruct* PayloadType)
{
if (const FGGA_GameplayEffectContext* Context = GetEffectContextPtr(EffectContext))
{
return Context->FindPayloadByType(PayloadType) != nullptr;
}
return false;
}
bool UGGA_GameplayEffectFunctionLibrary::GetContextPayload(FGameplayEffectContextHandle EffectContext, const UScriptStruct* PayloadType, FInstancedStruct& OutPayload)
{
if (FGGA_GameplayEffectContext* Context = GetEffectContextPtr(EffectContext))
{
if (FInstancedStruct* Found = Context->FindPayloadByType(PayloadType))
{
OutPayload = *Found;
return true;
}
}
return false;
}
FInstancedStruct UGGA_GameplayEffectFunctionLibrary::GetValidContextPayload(FGameplayEffectContextHandle EffectContext, const UScriptStruct* PayloadType, bool& bValid)
{
bValid = false;
if (FGGA_GameplayEffectContext* Context = GetEffectContextPtr(EffectContext))
{
if (FInstancedStruct* Found = Context->FindPayloadByType(PayloadType))
{
bValid = true;
return *Found;
}
}
return FInstancedStruct();
}
void UGGA_GameplayEffectFunctionLibrary::GetContextPayload(FGameplayEffectContextHandle EffectContext, const UScriptStruct* PayloadType, EGGA_ContextPayloadResult& ExecResult, int32& Value)
{
// We should never hit this! stubs to avoid NoExport on the class.
checkNoEntry();
}
DEFINE_FUNCTION(UGGA_GameplayEffectFunctionLibrary::execGetContextPayload)
{
P_GET_STRUCT_REF(FGameplayEffectContextHandle, EffectContext);
P_GET_OBJECT(const UScriptStruct, PayloadType);
P_GET_ENUM_REF(EGGA_ContextPayloadResult, ExecResult);
// Read wildcard Value input
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FStructProperty>(nullptr);
const FStructProperty* ValueProp = CastField<FStructProperty>(Stack.MostRecentProperty);
void* ValuePtr = Stack.MostRecentPropertyAddress;
P_FINISH;
P_NATIVE_BEGIN;
ExecResult = EGGA_ContextPayloadResult::NotValid;
if (!ValueProp || !ValuePtr || !PayloadType)
{
FBlueprintExceptionInfo ExceptionInfo(
EBlueprintExceptionType::AbortExecution,
LOCTEXT("InstancedStruct_GetInvalidValueWarning", "Failed to resolve the Value or PayloadType for Get Context Payload")
);
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
}
else
{
FInstancedStruct OutPayload;
if (GetContextPayload(EffectContext, PayloadType, OutPayload))
{
if (OutPayload.IsValid() && OutPayload.GetScriptStruct()->IsChildOf(ValueProp->Struct))
{
// Copy the struct data to the output Value
ValueProp->Struct->CopyScriptStruct(ValuePtr, OutPayload.GetMemory());
ExecResult = EGGA_ContextPayloadResult::Valid;
}
else
{
ExecResult = EGGA_ContextPayloadResult::NotValid;
}
}
else
{
ExecResult = EGGA_ContextPayloadResult::NotValid;
}
}
P_NATIVE_END;
}
void UGGA_GameplayEffectFunctionLibrary::SetContextPayload(FGameplayEffectContextHandle EffectContext, EGGA_ContextPayloadResult& ExecResult, const int32& Value)
{
// We should never hit this! stubs to avoid NoExport on the class.
checkNoEntry();
}
DEFINE_FUNCTION(UGGA_GameplayEffectFunctionLibrary::execSetContextPayload)
{
P_GET_STRUCT_REF(FGameplayEffectContextHandle, EffectContext);
P_GET_ENUM_REF(EGGA_ContextPayloadResult, ExecResult);
// Read wildcard Value input
Stack.MostRecentPropertyAddress = nullptr;
Stack.MostRecentPropertyContainer = nullptr;
Stack.StepCompiledIn<FStructProperty>(nullptr);
const FStructProperty* ValueProp = CastField<FStructProperty>(Stack.MostRecentProperty);
const void* ValuePtr = Stack.MostRecentPropertyAddress;
P_FINISH;
P_NATIVE_BEGIN;
ExecResult = EGGA_ContextPayloadResult::NotValid;
if (!ValueProp || !ValuePtr || !EffectContext.IsValid())
{
FBlueprintExceptionInfo ExceptionInfo(
EBlueprintExceptionType::AbortExecution,
LOCTEXT("InstancedStruct_SetInvalidValueWarning", "Failed to resolve Value or EffectContext for Set Instanced Struct Value")
);
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
}
else
{
// Create an FInstancedStruct from the input struct
FInstancedStruct Payload;
Payload.InitializeAs(ValueProp->Struct, static_cast<const uint8*>(ValuePtr));
if (Payload.IsValid())
{
// Call the existing SetPayload function
if (FGGA_GameplayEffectContext* ContextPtr = GetEffectContextPtr(EffectContext))
{
ContextPtr->AddOrOverwriteData(Payload);
ExecResult = EGGA_ContextPayloadResult::Valid;
}
}
else
{
FBlueprintExceptionInfo ExceptionInfo(
EBlueprintExceptionType::AbortExecution,
LOCTEXT("SetGameplayEffectContextPayload", "Failed to create valid InstancedStruct from Value")
);
FBlueprintCoreDelegates::ThrowScriptException(P_THIS, Stack, ExceptionInfo);
}
}
P_NATIVE_END;
}
#undef LOCTEXT_NAMESPACE