Program Listing for File GKAsyncTaskCooldownChanged.cpp

↰ Return to documentation for file (Source/Gamekit/Abilities/Blueprint/GKAsyncTaskCooldownChanged.cpp)

//  MIT License Copyright(c) 2020 Dan Kestranek

// Include
#include "Gamekit/Abilities/Blueprint/GKAsyncTaskCooldownChanged.h"

// Gamekit
#include "Gamekit/GKLog.h"
#include "Gamekit/Gamekit.h"

UGKAsyncTaskCooldownChanged *UGKAsyncTaskCooldownChanged::ListenForCooldownChange(
        UAbilitySystemComponent *AbilitySystemComponent,
        FGameplayTagContainer    InCooldownTags,
        bool                     InUseServerCooldown)
{
    UGKAsyncTaskCooldownChanged *ListenForCooldownChange = NewObject<UGKAsyncTaskCooldownChanged>();
    ListenForCooldownChange->ASC                         = AbilitySystemComponent;
    ListenForCooldownChange->CooldownTags                = InCooldownTags;
    ListenForCooldownChange->UseServerCooldown           = InUseServerCooldown;
    ListenForCooldownChange->bDestroyed                  = false;

    if (!IsValid(AbilitySystemComponent) || InCooldownTags.Num() < 1)
    {
        ListenForCooldownChange->EndTask();
        return nullptr;
    }

    TArray<FGameplayTag> CooldownTagArray;
    InCooldownTags.GetGameplayTagArray(CooldownTagArray);

    ListenForCooldownChange->DelegateHandles.Reserve(CooldownTagArray.Num() + 1);

    ListenForCooldownChange->DelegateHandles.Add(
            AbilitySystemComponent->OnActiveGameplayEffectAddedDelegateToSelf.AddUObject(
                    ListenForCooldownChange, &UGKAsyncTaskCooldownChanged::OnActiveGameplayEffectAddedCallback));

    for (FGameplayTag CooldownTag: CooldownTagArray)
    {
        // Invalid tags would make this crash
        if (!CooldownTag.IsValid() || CooldownTag == FGameplayTag::EmptyTag)
        {
            continue;
        }

        ListenForCooldownChange->DelegateHandles.Add(
                AbilitySystemComponent->RegisterGameplayTagEvent(CooldownTag, EGameplayTagEventType::NewOrRemoved)
                        .AddUObject(ListenForCooldownChange, &UGKAsyncTaskCooldownChanged::CooldownTagChanged));
    }

    return ListenForCooldownChange;
}

void UGKAsyncTaskCooldownChanged::EndTask()
{
    if (bDestroyed)
    {
        GK_ERROR(TEXT("Destroying object twice"));
        return;
    }

    if (IsValid(this))
    {
        SetReadyToDestroy();
    }
}

void UGKAsyncTaskCooldownChanged::SetReadyToDestroy()
{
    if (IsValid(ASC))
    {
        ASC->OnActiveGameplayEffectAddedDelegateToSelf.Remove(DelegateHandles[0]);

        TArray<FGameplayTag> CooldownTagArray;
        CooldownTags.GetGameplayTagArray(CooldownTagArray);

        int i = 0;
        for (FGameplayTag CooldownTag: CooldownTagArray)
        {
            i += 1;
            ASC->RegisterGameplayTagEvent(CooldownTag, EGameplayTagEventType::NewOrRemoved).Remove(DelegateHandles[i]);
        }
    }

    Super::SetReadyToDestroy();
    bDestroyed = true;
}

void UGKAsyncTaskCooldownChanged::OnActiveGameplayEffectAddedCallback(UAbilitySystemComponent    *Target,
                                                                      const FGameplayEffectSpec  &SpecApplied,
                                                                      FActiveGameplayEffectHandle ActiveHandle)
{
    FGameplayTagContainer AssetTags;
    SpecApplied.GetAllAssetTags(AssetTags);

    FGameplayTagContainer GrantedTags;
    SpecApplied.GetAllGrantedTags(GrantedTags);

    TArray<FGameplayTag> CooldownTagArray;
    CooldownTags.GetGameplayTagArray(CooldownTagArray);

    for (FGameplayTag CooldownTag: CooldownTagArray)
    {
        if (AssetTags.HasTagExact(CooldownTag) || GrantedTags.HasTagExact(CooldownTag))
        {
            float TimeRemaining = 0.0f;
            float Duration      = 0.0f;
            // Expecting cooldown tag to always be first tag
            FGameplayTagContainer CooldownTagContainer(GrantedTags.GetByIndex(0));
            GetCooldownRemainingForTag(CooldownTagContainer, TimeRemaining, Duration);

            if (ASC->GetOwnerRole() == ROLE_Authority)
            {
                // Player is Server
                OnCooldownBegin.Broadcast(CooldownTag, TimeRemaining, Duration);
            }
            else if (!UseServerCooldown && SpecApplied.GetContext().GetAbilityInstance_NotReplicated())
            {
                // Client using predicted cooldown
                OnCooldownBegin.Broadcast(CooldownTag, TimeRemaining, Duration);
            }
            else if (UseServerCooldown && SpecApplied.GetContext().GetAbilityInstance_NotReplicated() == nullptr)
            {
                // Client using Server's cooldown. This is Server's corrective cooldown GE.
                OnCooldownBegin.Broadcast(CooldownTag, TimeRemaining, Duration);
            }
            else if (UseServerCooldown && SpecApplied.GetContext().GetAbilityInstance_NotReplicated())
            {
                // Client using Server's cooldown but this is predicted cooldown GE.
                // This can be useful to gray out abilities until Server's cooldown comes in.
                OnCooldownBegin.Broadcast(CooldownTag, -1.0f, -1.0f);
            }
        }
    }
}

void UGKAsyncTaskCooldownChanged::CooldownTagChanged(const FGameplayTag CooldownTag, int32 NewCount)
{

    if (NewCount == 0)
    {
        OnCooldownEnd.Broadcast(CooldownTag, -1.0f, -1.0f);
    }
}

bool UGKAsyncTaskCooldownChanged::GetCooldownRemainingForTag(FGameplayTagContainer InCooldownTags,
                                                             float                &TimeRemaining,
                                                             float                &CooldownDuration)
{
    if (IsValid(ASC) && InCooldownTags.Num() > 0)
    {
        TimeRemaining    = 0.f;
        CooldownDuration = 0.f;

        FGameplayEffectQuery const  Query = FGameplayEffectQuery::MakeQuery_MatchAnyOwningTags(InCooldownTags);
        TArray<TPair<float, float>> DurationAndTimeRemaining = ASC->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;
}