Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composing Ability Tasks #138

Open
reisandbeans opened this issue Sep 9, 2024 · 1 comment
Open

Composing Ability Tasks #138

reisandbeans opened this issue Sep 9, 2024 · 1 comment

Comments

@reisandbeans
Copy link

I often find myself using very similar Ability Tasks in different abilities. For example I have to play a montage and wait for input release, or play a montage and wait for target data, etc, so I was thinking of creating ability tasks that are actually a composition of other ability tasks. Yes, it's less flexible, but I've had enough use cases where I think it's worth creating something reusable.

That said, are there caveats of doing it that I should be aware of? For example, are there downsides of manually creating ability tasks inside of other ability tasks, manually cancelling and managing them, etc?

@onx2
Copy link

onx2 commented Dec 18, 2024

I had a similar situation and decided to just make a new ability task for the specific scenario I needed. For example, wait for a confirm or cancel and also retrieve the target data under the cursor when that happens rather than trying to reuse existing tasks within other tasks.

#pragma once

#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "WaitConfirmOrCancelWithData.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FWaitConfirmCancelWithDataSignature, const FGameplayAbilityTargetDataHandle&, DataHandle);

/**
 * Ability Task to wait for confirm or cancel input, collecting and sending target data under the cursor in response.
 */
UCLASS()
class AURORASVEIL_API UWaitConfirmOrCancelWithData : public UAbilityTask
{
	GENERATED_BODY()
public:
	UPROPERTY(BlueprintAssignable)
	FWaitConfirmCancelWithDataSignature OnConfirm;

	UPROPERTY(BlueprintAssignable)
	FWaitConfirmCancelWithDataSignature OnCancel;

	UPROPERTY(BlueprintReadWrite, Category = "Ability|Tasks")
	bool bIgnoreSelf;

	UPROPERTY(BlueprintReadWrite, Category = "Ability|Tasks")
	TEnumAsByte<ECollisionChannel> TraceChannel = ECC_Visibility;

	UFUNCTION(BlueprintCallable, meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true"), Category = "Veil|AbilityTasks")
	static UWaitConfirmOrCancelWithData* WaitConfirmOrCancelWithData(UGameplayAbility* OwningAbility, bool bInIgnoreSelf, ECollisionChannel InTraceChannel);

	virtual void Activate() override;

protected:
	virtual void OnDestroy(bool AbilityEnding) override;

private:
	bool bRegisteredCallbacks;
	void SendMouseCursorData(bool bCancelled) const;

	void OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& DataHandle, FGameplayTag ActivationTag) const;

	// Local Confirm/Cancel callbacks
	UFUNCTION() void OnLocalConfirmCallback();
	UFUNCTION() void OnLocalCancelCallback();
};
#include "AbilitySystem/AbilityTask/WaitConfirmOrCancelWithData.h"
#include "AbilitySystemComponent.h"
#include "VeilGameplayTags.h"

UWaitConfirmOrCancelWithData* UWaitConfirmOrCancelWithData::WaitConfirmOrCancelWithData(UGameplayAbility* OwningAbility, bool bInIgnoreSelf, ECollisionChannel InTraceChannel)
{
	UWaitConfirmOrCancelWithData* Task = NewAbilityTask<UWaitConfirmOrCancelWithData>(OwningAbility);
	Task->bIgnoreSelf = bInIgnoreSelf;
	Task->TraceChannel = InTraceChannel;
	return Task;
}

void UWaitConfirmOrCancelWithData::Activate()
{
	if (AbilitySystemComponent.IsValid() && Ability)
	{
		const FGameplayAbilityActorInfo* Info = Ability->GetCurrentActorInfo();

		if (Info->IsLocallyControlled())
		{
			AbilitySystemComponent->GenericLocalConfirmCallbacks.AddDynamic(this, &UWaitConfirmOrCancelWithData::OnLocalConfirmCallback);
			AbilitySystemComponent->GenericLocalCancelCallbacks.AddDynamic(this, &UWaitConfirmOrCancelWithData::OnLocalCancelCallback);

			bRegisteredCallbacks = true;
		}
		else
		{
			const FGameplayAbilitySpecHandle SpecHandle = GetAbilitySpecHandle();
			const FPredictionKey ActivationPredictionKey = GetActivationPredictionKey();
			AbilitySystemComponent.Get()->AbilityTargetDataSetDelegate(SpecHandle, ActivationPredictionKey).AddUObject(this, &UWaitConfirmOrCancelWithData::OnTargetDataReplicatedCallback);
			const bool bCalledDelegate = AbilitySystemComponent.Get()->CallReplicatedTargetDataDelegatesIfSet(SpecHandle, ActivationPredictionKey);
			if (!bCalledDelegate)
			{
				SetWaitingOnRemotePlayerData();
			}
		}
	}
	else
	{
		EndTask();
	}
}

// Callbacks that when triggered will get the target data under the cursor and broadcast it
void UWaitConfirmOrCancelWithData::OnLocalConfirmCallback() { SendMouseCursorData(false); }
void UWaitConfirmOrCancelWithData::OnLocalCancelCallback() { SendMouseCursorData(true); }

// Finds the target data under the cursor, locally, and then replicates it to the server and broadcasts it to the client
void UWaitConfirmOrCancelWithData::SendMouseCursorData(bool bCancelled) const
{
	FScopedPredictionWindow ScopedPrediction(AbilitySystemComponent.Get(), IsPredictingClient());
	
	const APlayerController* PC = Ability->GetCurrentActorInfo()->PlayerController.Get();
	const APawn* PlayerPawn = PC->GetPawn();

	FCollisionQueryParams QueryParams;
	if (bIgnoreSelf)
	{
		QueryParams.AddIgnoredActor(PlayerPawn);
	}

	FVector WorldLocation, WorldDirection;
	float X, Y = 0.f;
	PC->GetMousePosition(X, Y);
	PC->DeprojectScreenPositionToWorld(X, Y, WorldLocation, WorldDirection);

	const FVector TraceStart = WorldLocation;
	const FVector TraceEnd = WorldLocation + WorldDirection * 10000.0f;

	FHitResult CursorHit;
	PC->GetWorld()->LineTraceSingleByChannel(CursorHit, TraceStart, TraceEnd, TraceChannel, QueryParams);

	FGameplayAbilityTargetDataHandle DataHandle;
	FGameplayAbilityTargetData_SingleTargetHit* Data = new FGameplayAbilityTargetData_SingleTargetHit();
	Data->HitResult = CursorHit;
	DataHandle.Add(Data);

	AbilitySystemComponent->ServerSetReplicatedTargetData(
		GetAbilitySpecHandle(),
		GetActivationPredictionKey(),
		DataHandle,
		bCancelled ? FVeilGameplayTags::Input_CancelAbility : FVeilGameplayTags::Input_ConfirmAbility,
		AbilitySystemComponent->ScopedPredictionKey);

	// Broadcasting to the server or clients
	if (ShouldBroadcastAbilityTaskDelegates())
	{
		if (bCancelled)
		{
			OnCancel.Broadcast(DataHandle);
		}
		else
		{
			OnConfirm.Broadcast(DataHandle);
		}
	}
}

// When the server receives the target data, broadcast it out
void UWaitConfirmOrCancelWithData::OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& DataHandle, const FGameplayTag ActivationTag) const
{
	AbilitySystemComponent->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey());
	if (ShouldBroadcastAbilityTaskDelegates())
	{
		if (ActivationTag.MatchesTagExact(FVeilGameplayTags::Input_ConfirmAbility))
		{
			OnConfirm.Broadcast(DataHandle);
		}
		else
		{
			OnCancel.Broadcast(DataHandle);
		}
	}
}

// Cleanup
void UWaitConfirmOrCancelWithData::OnDestroy(const bool AbilityEnding)
{
	if (bRegisteredCallbacks && AbilitySystemComponent.IsValid())
	{
		AbilitySystemComponent->GenericLocalConfirmCallbacks.RemoveDynamic(this, &UWaitConfirmOrCancelWithData::OnLocalConfirmCallback);
		AbilitySystemComponent->GenericLocalCancelCallbacks.RemoveDynamic(this, &UWaitConfirmOrCancelWithData::OnLocalCancelCallback);
	}

	Super::OnDestroy(AbilityEnding);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants