Adjusting Durations/Cooldowns of Active Gameplay Effects

So a common question is, how can i increase/decrease cooldowns or duration based effects when they are active. I did some research, and came up with what i think is the nicest way to do it, and it uses GameplayEffects to achieve this.

First we need to make a dummy attribute, you can put this in any attribute set you like (i recommend something like PlayerSet or CharacterSet.)

UPROPERTY(BlueprintReadOnly, Meta = (HideFromModifiers, AllowPrivateAccess = true))
FKaosGameplayAttributeData ActiveEffectDuration;
ATTRIBUTE_ACCESSORS(ThisClass, ActiveEffectDuration);

This attribute will not show up in your Gameplay Effect BP’s or anywhere in the editor. (That is what HideFromModifiers does).

Now we do need a few functions in your project specific ASC.

void UKaosAbilitySystemComponent::MarkActiveGameplayEffectDirty(FActiveGameplayEffect* ActiveGE)
{
	if (ActiveGE)
	{
		ActiveGameplayEffects.MarkItemDirty(*ActiveGE);
	}
}

void UKaosAbilitySystemComponent::CheckActiveEffectDuration(const FActiveGameplayEffectHandle& Handle)
{
	ActiveGameplayEffects.CheckDuration(Handle);
}

FActiveGameplayEffect* UKaosAbilitySystemComponent::GetActiveGameplayEffect_Mutable(const FActiveGameplayEffectHandle Handle)
{
	return ActiveGameplayEffects.GetActiveGameplayEffect(Handle);
}

TArray<FActiveGameplayEffectHandle> UKaosAbilitySystemComponent::GetAllActiveEffectHandles() const
{
	return ActiveGameplayEffects.GetAllActiveEffectHandles();
}

Header file:
	/** Return a mutable pointer to the ActiveGameplayEffect from the supplied handle. */
	FActiveGameplayEffect* GetActiveGameplayEffect_Mutable(FActiveGameplayEffectHandle Handle);

	/** Returns all active gameplay effect handles */
	TArray<FActiveGameplayEffectHandle> GetAllActiveEffectHandles() const;

	/** Marks the ActiveGameplayEffect as dirty for replication purposes */
	void MarkActiveGameplayEffectDirty(FActiveGameplayEffect* ActiveGE);

	/** Checks the active effect duration and runs any logic, checks to see if the GE is expired and removes it. */
	void CheckActiveEffectDuration(const FActiveGameplayEffectHandle& Handle);

These functions expose some things we need, first one allows us to mark a GE as dirty for replication purposes, second one checks the duration of the GE after we manipulate the value. The third one, returns a mutable pointer to the ActiveGameplayEffect, and last, returns a copy of all ActiveEffectHandles.

Now we get on to the actual Gameplay Effect Execution Calculation we will be using.
We need to define a struct, which will capture our ActiveEffectDuration attribute.

struct FKaosActiveDurationStatics
{
	//Target
	FGameplayEffectAttributeCaptureDefinition TargetActiveEffectDurationDef;

	FKaosActiveDurationStatics()
	{
		//Source captures
		TargetActiveEffectDurationDef = FGameplayEffectAttributeCaptureDefinition(UKaosPlayerSet::GetActiveEffectDurationAttribute(), EGameplayEffectAttributeCaptureSource::Target, true);
	}
};

static FKaosActiveDurationStatics& KaosActiveDurationStatics()
{
	static FKaosActiveDurationStatics Statics;
	return Statics;
}

Now we can define the rest of the calculation.

UKaosActiveEffectDurationExecution::UKaosActiveEffectDurationExecution()
{
	//Source Captures
	RelevantAttributesToCapture.Add(KaosActiveDurationStatics().TargetActiveEffectDurationDef);
}

void UKaosActiveEffectDurationExecution::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
#if WITH_SERVER_CODE
	UKaosAbilitySystemComponent* TargetASC = Cast<UKaosAbilitySystemComponent>(ExecutionParams.GetTargetAbilitySystemComponent());

	//Used to hold the evaluate params for the loop.
	FAggregatorEvaluateParameters EvaluateParameters;

	//Get all active effect handles.
	TArray<FActiveGameplayEffectHandle> Handles = TargetASC->GetAllActiveEffectHandles();

	//Loop over all active Effect handles.
	for (const FActiveGameplayEffectHandle Handle : Handles)
	{
		FActiveGameplayEffect* ActiveGE = TargetASC->GetActiveGameplayEffect_Mutable(Handle);

		//Didn't find an active GE or GE is not duration based, skip it. Only works on duration based GE's.
		if (!ActiveGE || ActiveGE->Spec.Def->DurationPolicy != EGameplayEffectDurationType::HasDuration)
		{
			continue;
		}

		//Our new duration (calculated from AttemptCalculateCapturedAttributeMagnitudeWithBase)
		float NewDuration = 0.f;

		//We get the current iterated GE's asset tags and use this in the calculation
		FGameplayTagContainer Tags;
		ActiveGE->Spec.GetAllAssetTags(Tags);

		//Set the evalute params target tags to the current iterated GE's asset tags.
		EvaluateParameters.TargetTags = &Tags;

		//Here we run the calculation to get the new duration, passing in the current iterated GE's duration as a base value, before it gets manipulated.
		ExecutionParams.AttemptCalculateCapturedAttributeMagnitudeWithBase(KaosActiveDurationStatics().TargetActiveEffectDurationDef, EvaluateParameters, ActiveGE->GetDuration(), NewDuration);

		//We set the GE's new duration, but clamp it so it is never 0.
		ActiveGE->Spec.Duration = FMath::Max(NewDuration, SMALL_NUMBER);

		//Mark the GE as dirty, so it can be replicated.
		TargetASC->MarkActiveGameplayEffectDirty(ActiveGE);
 
		//This will remove the GE if it has expired and update the timers if necessary
		TargetASC->CheckActiveEffectDuration(Handle);
	}
#endif
}

Most of the code is commented here, but in brief, we iterate all active GE’s, only get the ones with a duration, then we calculate the new duration, and set it dirty and then check the duration on it (to see if it should expire).

Here is an example GE, that will reduce cooldown by 25% when this GE is applied

Hope this helps, any issues or problems, check out my Discord in About Me, or join Unreal Slackers.

,