第一次提交

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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