第一次提交
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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();
|
||||
//}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GGA_GameplayAbilityTargetData_Payload.h"
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Phases/GGA_GamePhaseLog.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogGGA_GamePhase)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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永远不会发生,因为我们一次性Start,Confirm,然后马上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;
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user