Skip to content

Instantly share code, notes, and snippets.

@MajorTomAW
Last active January 3, 2026 23:00
Show Gist options
  • Select an option

  • Save MajorTomAW/3cb303ddc2e443a70601de5b39d067dd to your computer and use it in GitHub Desktop.

Select an option

Save MajorTomAW/3cb303ddc2e443a70601de5b39d067dd to your computer and use it in GitHub Desktop.
//@see https://dev.epicgames.com/community/learning/tutorials/bEJV/unreal-engine-creating-a-targeting-ability-task-using-epic-s-targetingsystem-plugin
#include "AbilitySystem/Tasks/AbilityTask_WaitPerformTargeting.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystemLog.h"
#include "TargetingSystem/TargetingSubsystem.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AbilityTask_WaitPerformTargeting)
UAbilityTask_WaitPerformTargeting::UAbilityTask_WaitPerformTargeting()
: bForceTargetingOnServer(false)
, bSkipReplicatingDataToServer(false)
, bUseAsyncTargeting(false)
{
}
UAbilityTask_WaitPerformTargeting* UAbilityTask_WaitPerformTargeting::WaitPerformTargeting(
UGameplayAbility* OwningAbility,
FName TaskInstanceName,
UTargetingPreset* TargetingPreset,
bool bForceTargetingOnServer,
bool bSkipReplicatingDataToServer,
bool bUseAsyncTargeting,
AActor* OverrideSourceActor)
{
if (!IsValid(TargetingPreset))
{
return nullptr;
}
UAbilityTask_WaitPerformTargeting* NewTask = NewAbilityTask<UAbilityTask_WaitPerformTargeting>(OwningAbility, TaskInstanceName);
NewTask->TargetingPreset = TargetingPreset;
NewTask->bForceTargetingOnServer = bForceTargetingOnServer;
NewTask->bSkipReplicatingDataToServer = bSkipReplicatingDataToServer;
NewTask->bUseAsyncTargeting = bUseAsyncTargeting;
NewTask->OverrideSourceActor = OverrideSourceActor;
return NewTask;
}
UAbilityTask_WaitPerformTargeting* UAbilityTask_WaitPerformTargeting::WaitPerformTargetFiltering(
UGameplayAbility* OwningAbility,
FName TaskInstanceName,
UTargetingPreset* TargetingPreset,
const TArray<AActor*> InTargets,
bool bForceTargetingOnServer,
bool bSkipReplicatingDataToServer,
bool bUseAsyncTargeting,
AActor* OverrideSourceActor)
{
if (!IsValid(TargetingPreset))
{
return nullptr;
}
UAbilityTask_WaitPerformTargeting* NewTask = NewAbilityTask<UAbilityTask_WaitPerformTargeting>(OwningAbility, TaskInstanceName);
NewTask->TargetingPreset = TargetingPreset;
NewTask->bForceTargetingOnServer = bForceTargetingOnServer;
NewTask->bSkipReplicatingDataToServer = bSkipReplicatingDataToServer;
NewTask->bUseAsyncTargeting = bUseAsyncTargeting;
NewTask->OverrideSourceActor = OverrideSourceActor;
NewTask->InitialTargets = InTargets;
return NewTask;
}
void UAbilityTask_WaitPerformTargeting::Activate()
{
// Sanity checks
if (!IsValid(Ability) || !IsValid(TargetingPreset))
{
return;
}
UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
if (!ensure(IsValid(ASC)))
{
return;
}
// We could have provided a different source actor
AActor* SourceActor = IsValid(OverrideSourceActor)
? OverrideSourceActor.Get()
: GetAvatarActor();
if (!ensure(IsValid(SourceActor)))
{
return;
}
UTargetingSubsystem* TargetingSub = UTargetingSubsystem::Get(SourceActor->GetWorld());
if (!ensure(TargetingSub))
{
return;
}
const bool bIsLocallyControlled = Ability->GetCurrentActorInfo()->IsLocallyControlled();
// Locally Controlled: Okay
// Authority: Okay
// Not Locally Controlled and not force on server: Not Okay!
const bool bShouldProduceTargetDataNow = bForceTargetingOnServer || bIsLocallyControlled;
// If not locally controlled (server for remote client), see if TargetData was already sent
// Otherwise, register callback for when it does get there.
if (!bIsLocallyControlled)
{
// Register with the TargetData callbacks if we are expecting client to send them
if (!bForceTargetingOnServer)
{
const FGameplayAbilitySpecHandle SpecHandle = GetAbilitySpecHandle();
const FPredictionKey ActivationPredictionKey = GetActivationPredictionKey();
// Since multi-fire is supported, we still need to hook up the callbacks
ASC->AbilityTargetDataSetDelegate(SpecHandle, ActivationPredictionKey)
.AddUObject(this, &ThisClass::OnTargetDataReadyCallback);
ASC->AbilityTargetDataCancelledDelegate(SpecHandle, ActivationPredictionKey)
.AddUObject(this, &ThisClass::OnTargetDataCancelledCallback);
ASC->CallReplicatedTargetDataDelegatesIfSet(SpecHandle, ActivationPredictionKey);
SetWaitingOnRemotePlayerData();
}
}
if (bShouldProduceTargetDataNow)
{
FTargetingSourceContext SourceContext;
SourceContext.SourceActor = SourceActor;
// Attempt to fill in any additional targets (for filtering only)
FTargetingRequestHandle TargetingHandle = UTargetingSubsystem::MakeTargetRequestHandle(TargetingPreset, SourceContext);
SetupInitialTargetsForRequest(TargetingHandle);
FTargetingRequestDelegate Delegate = FTargetingRequestDelegate::CreateUObject(this, &ThisClass::OnTargetingRequested);
// Now either start an async or immediate targeting request
if (bUseAsyncTargeting)
{
UE_LOG(LogAbilitySystem, Verbose, TEXT("Starting async ability targeting task..."))
FTargetingAsyncTaskData& AsyncTask = FTargetingAsyncTaskData::FindOrAdd(TargetingHandle);
AsyncTask.bReleaseOnCompletion = true;
TargetingSub->StartAsyncTargetingRequestWithHandle(TargetingHandle, Delegate);
}
else
{
UE_LOG(LogAbilitySystem, Verbose, TEXT("Starting immediate ability targeting task..."))
FTargetingImmediateTaskData& ImmediateTask = FTargetingImmediateTaskData::FindOrAdd(TargetingHandle);
ImmediateTask.bReleaseOnCompletion = true;
TargetingSub->ExecuteTargetingRequestWithHandle(TargetingHandle, Delegate);
}
}
}
void UAbilityTask_WaitPerformTargeting::ExternalCancel()
{
if (ShouldBroadcastAbilityTaskDelegates())
{
OnCancelled.Broadcast(FGameplayAbilityTargetDataHandle());
}
Super::ExternalCancel();
}
void UAbilityTask_WaitPerformTargeting::OnDestroy(bool bInOwnerFinished)
{
Super::OnDestroy(bInOwnerFinished);
}
void UAbilityTask_WaitPerformTargeting::OnTargetDataCancelledCallback()
{
// Client cancelled this Targeting Task (we're the server)
if (ShouldBroadcastAbilityTaskDelegates())
{
OnCancelled.Broadcast(FGameplayAbilityTargetDataHandle());
}
EndTask();
}
void UAbilityTask_WaitPerformTargeting::OnTargetDataReadyCallback(
const FGameplayAbilityTargetDataHandle& TargetData,
FGameplayTag ApplicationTag)
{
// Valid Target Data was replicated to use (we are server, was sent from client)
FGameplayAbilityTargetDataHandle MutableData = TargetData;
if (UAbilitySystemComponent* ASC = AbilitySystemComponent.Get())
{
ASC->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey());
}
if (ShouldBroadcastAbilityTaskDelegates())
{
OnTargeted.Broadcast(MutableData);
}
EndTask();
}
void UAbilityTask_WaitPerformTargeting::SetupInitialTargetsForRequest(FTargetingRequestHandle RequestHandle) const
{
if (InitialTargets.IsEmpty() ||!RequestHandle.IsValid())
{
return;
}
// Fill in initial targetin results for filtering task
FTargetingDefaultResultsSet& TargetingResults = FTargetingDefaultResultsSet::FindOrAdd(RequestHandle);
for (AActor* Target : InitialTargets)
{
if (!IsValid(Target))
{
continue;
}
// Make sure to not add targets twice
const bool bAddResult = !TargetingResults.TargetResults.ContainsByPredicate(
[Target] (const FTargetingDefaultResultData& Data) -> bool
{
return Data.HitResult.GetActor() == Target;
});
if (!bAddResult)
{
continue;
}
FTargetingDefaultResultData* ResultData = new(TargetingResults.TargetResults) FTargetingDefaultResultData();
ResultData->HitResult.HitObjectHandle = FActorInstanceHandle(Target);
ResultData->HitResult.Location = Target->GetActorLocation();
}
}
void UAbilityTask_WaitPerformTargeting::OnTargetingRequested(FTargetingRequestHandle TargetingHandle)
{
UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
if (!ensure(IsValid(ASC)))
{
EndTask();
return;
}
// Fill in the target data with the hit results from the targeting task
FGameplayAbilityTargetDataHandle TargetData;
TArray<FHitResult> HitResults;
if (TargetingHandle.IsValid())
{
if (FTargetingDefaultResultsSet* ResultData = FTargetingDefaultResultsSet::Find(TargetingHandle))
{
for (const FTargetingDefaultResultData& TargetingResult : ResultData->TargetResults)
{
// Converting Targeting Result into Target Data
FGameplayAbilityTargetData_SingleTargetHit* Data = new FGameplayAbilityTargetData_SingleTargetHit();
Data->HitResult = TargetingResult.HitResult;
TargetData.Add(Data);
}
}
}
FScopedPredictionWindow ScopedPrediction(ASC, ShouldReplicateDataToServer());
if (IsPredictingClient())
{
// If the server didn't target, we need to send the targeting data now!
if (!bForceTargetingOnServer && !bSkipReplicatingDataToServer)
{
FGameplayTag ApplicationTag;
ASC->CallServerSetReplicatedTargetData(
GetAbilitySpecHandle(),
GetActivationPredictionKey(),
TargetData,
ApplicationTag,
ASC->ScopedPredictionKey);
}
}
if (ShouldBroadcastAbilityTaskDelegates())
{
OnTargeted.Broadcast(TargetData);
}
EndTask();
}
bool UAbilityTask_WaitPerformTargeting::ShouldReplicateDataToServer() const
{
if (!IsValid(Ability))
{
return false;
}
/* Send TargetData to server IF:
* - we are the client.
* - We don't want to skip replication to server.
* - This task can't produce target data on server. */
const FGameplayAbilityActorInfo* Info = Ability->GetCurrentActorInfo();
if (!Info->IsNetAuthority() &&
!bSkipReplicatingDataToServer &&
!bForceTargetingOnServer)
{
return true;
}
return false;
}
//@see https://dev.epicgames.com/community/learning/tutorials/bEJV/unreal-engine-creating-a-targeting-ability-task-using-epic-s-targetingsystem-plugin
#pragma once
#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
// We need those headers as well
#include "Abilities/Tasks/AbilityTask_WaitTargetData.h"
#include "TargetingSystem/TargetingPreset.h"
#include "AbilityTask_WaitPerformTargeting.generated.h"
/** Ability task to both perform targeting and filtering. */
UCLASS()
class UAbilityTask_WaitPerformTargeting : public UAbilityTask
{
GENERATED_BODY()
public:
UAbilityTask_WaitPerformTargeting();
/** Called when the targeting request has been completed and target data is valid. */
UPROPERTY(BlueprintAssignable)
FWaitTargetDataDelegate OnTargeted;
/** Called when targeting request was cancelled, target data will NOT be valid. */
UPROPERTY(BlueprintAssignable)
FWaitTargetDataDelegate OnCancelled;
/** Performs a targeting request based on the given Targeting Preset.
* @param bForceTargetingOnServer If true, the server will also perform targeting instead of relying on the data the client sent
* @param bSkipReplicatingDataToServer If true, server won't receive generated target data produced by the client.
*/
UFUNCTION(BlueprintCallable, Category="Ability|Tasks", DisplayName="Wait Perform Targeting Ability Task", meta=(HidePin=OwningAbility, DefaultToSelf=OwningAbility, BlueprintInternalUseOnly=true))
static UAbilityTask_WaitPerformTargeting* WaitPerformTargeting(UGameplayAbility* OwningAbility, FName TaskInstanceName, UTargetingPreset* TargetingPreset, bool bForceTargetingOnServer, bool bSkipReplicatingDataToServer, bool bUseAsyncTargeting, AActor* OverrideSourceActor = nullptr);
/** Performs a target filtering request based on the given Targeting Preset.
* @param bForceTargetingOnServer If true, the server will also perform targeting instead of relying on the data the client sent
* @param bSkipReplicatingDataToServer If true, server won't receive generated target data produced by the client.
*/
UFUNCTION(BlueprintCallable, Category="Ability|Tasks", DisplayName="Wait Perform Target Filtering Ability Task", meta=(HidePin=OwningAbility, DefaultToSelf=OwningAbility, BlueprintInternalUseOnly=true))
static UAbilityTask_WaitPerformTargeting* WaitPerformTargetFiltering(UGameplayAbility* OwningAbility, FName TaskInstanceName, UTargetingPreset* TargetingPreset, const TArray<AActor*> InTargets, bool bForceTargetingOnServer, bool bSkipReplicatingDataToServer, bool bUseAsyncTargeting, AActor* OverrideSourceActor = nullptr);
protected:
//~ Begin UAbilityTask Interface
virtual void Activate() override;
virtual void ExternalCancel() override;
virtual void OnDestroy(bool bInOwnerFinished) override;
//~ End UAbilityTask Interface
UFUNCTION()
void OnTargetDataCancelledCallback();
UFUNCTION()
void OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& TargetData, FGameplayTag ApplicationTag);
/** Helper function to push the InitialTargets array to the DataStore for Filtering requests. */
void SetupInitialTargetsForRequest(FTargetingRequestHandle RequestHandle) const;
void OnTargetingRequested(FTargetingRequestHandle TargetingHandle);
virtual bool ShouldReplicateDataToServer() const;
private:
/** Targeting preset to request. */
UPROPERTY()
TObjectPtr<UTargetingPreset> TargetingPreset;
/** Initial targets to pass into the request (only populated for filtering requests). */
UPROPERTY()
TArray<TObjectPtr<AActor>> InitialTargets;
/** Whether the server should be forced to always generate target data too. */
uint8 bForceTargetingOnServer:1;
/** Whether to skip replicating the generated target data to the server. */
uint8 bSkipReplicatingDataToServer:1;
/** Whether to perform this request immediately or async. */
uint8 bUseAsyncTargeting:1;
UPROPERTY()
TObjectPtr<AActor> OverrideSourceActor;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment