Understanding Gameplay Effect Execution Calculations!

Gameplay Effect Execution Calculation is a way to manipulate attributes base value. They are not stateful and can not be predicted (any attribute mutated this way is done so on it’s base value). But they are super powerful!
Below I will explain some of the stuff about Gameplay Effect Execution Calculations and how to do general calculations. This is not exhaustive, but should help you understand things.

The main function you override is

virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;

This has two parameters, ExecutionParams which contains all the incoming data and OutExecutionOutput which will contain all outputs from the calculation.

We will focus first on ExecutionParams. It has a few functions we can use to get info about the applied effect, its target and source ability system component, etc. I will list them below with a bit of info:

GetOwningSpec - Returns the owning spec of the effect that is running this execution.
GetTargetAbilitySystemComponent - Return the ability system component of the owner being affected by this Effect.
GetSourceAbilitySystemComponent - Returns the ability system component of the owner who created this Effect.
GetPassedInTags - Returns all tags passed into this execution calculation (will discuss this more later.)
GetPredictionKey - Returns the prediction key associated with the Effect,.

These are the basic getters for Information, now we have some calculation specific functions. These will require some additional information so will be discussed per block with an example.

AttemptCalculateCapturedAttributeMagnitude

This will attempt to calculate a magnitude value for a specific attribute, you pass in the required attribute definition, the evaluation parameters, and it will calculate an output magnitude. Example here:

float DamageResistance = 0.0f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(KaosDamageStatics().DamageResistanceDef, EvaluateParameters, DamageResistance);

The way this works is, it will grab the value from the attribute set, go through Effects and see if anything will change the DamageResistance, and spit it out to the DamageResistance float. This is now the total DamageResistance the actor has for example. It will return true or false if the attribute was captured successfully.

AttemptCalculateCapturedAttributeMagnitudeWithBase

This one is an extension to the above, except you can pass in a base value to start with. Functionality is the same as above.

float CriticalZoneMultiplier = 1.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitudeWithBase(KaosDamageStatics().CritDamageZoneMultiplierDef, EvaluateParameters, DamageStats->Multiplier_DamageZone_Critical, CriticalZoneMultiplier);

As you can see here, we pass in the same details as previous one, but with the Addition of a new float, DamageStats->Multiplier_DamageZone_Critical. This serves as the base value for the calculation and is added in addition to the attributes base value and the channels value. For example you may pass in a base of 30, and the evaluator finds some modifiers for that attribute, it will use the base of 30 for the calculation. You might have a GE which gives 1.5 multiplier to the CriticalZoneMultiplier, your returned value would then be 45.

Source and Target Tags

	//Grab the tags from source and target
	const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
	const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();

	//Setup our evaluate params
	FAggregatorEvaluateParameters EvaluateParameters;
	EvaluateParameters.SourceTags = SourceTags;
	EvaluateParameters.TargetTags = TargetTags;

The above grabs the tags from the source and the target, and stores them in a special struct we can pass along to our evaluators, this allows the system to determine what GE’s should be relevant for calculation. For example you might have a GE which has an attribute modifier that requires the tag “Damage.Type.Fire” on the source, if so it will use this modifier in the calculation, but any GE with say ignore “Damage.Type.Fire”, will not be used in the calculation by the aggregator.

Output Modifiers

When you have calculated your value, you will need to return the attribute to modified (if any), this is done with

OutExecutionOutput.AddOutputModifier

An example for a damage execution is shown here

	if (DamageDone > 0.0f)
	{
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(UAresHealthSet::GetDamageAttribute(), EGameplayModOp::Additive, DamageDone));
	}

This is pretty straightforward, we simply set an Attribute to the DamageDone value, and mark it as Additive. This will then get passed to the AttributeSet for processing and adjusting the attribute.

MarkConditionalGameplayEffectsToTrigger

When you have done your calculation, you might want to go an trigger any conditional gameplay effects, but only if the calculation was successful, you do this with

OutExecutionOutput.MarkConditionalGameplayEffectsToTrigger();

MarkStackCountHandledManually

If you have handled the GE’s stacking manually, you can tell the spec this, i have not found use for this yet, but the comment says

Mark that the execution has manually handled the stack count and the GE system should not attempt to automatically act upon it for emitted modifiers
OutExecutionOutput.MarkStackCountHandledManually();

MarkGameplayCuesHandledManually

If you played the Gameplay Cues in the execution or via some other mechanism, you can disable the GE from firing its cues after the calculation

OutExecutionOutput.MarkGameplayCuesHandledManually();

Simple Damage Calculation

Utilizing some of the stuff above, this is a very simple Damage Execution Calculation, which get the Damage value from the ability providing the Damage (or via a SetByCaller damage value)

/*
 * Struct to hold our attributes we want to access
*/
struct FAresDamageStatics
{
	/*
     * Source ASC Attributes
    */
    // Base Damage for the Actor/Character/Player
	FGameplayEffectAttributeCaptureDefinition OutgoingBaseDamageDef;

    // Outgoing Ability Damage for the actor (can be SetByCaller or defined in the GE)
	FGameplayEffectAttributeCaptureDefinition OutgoingAbilityDamageDef;

	/*
     * Target ASC Attributes
    */
    // Damage attribute we set on the target which will be used to adjust its health attribute.
	FGameplayEffectAttributeCaptureDefinition DamageDef;

	FAresDamageStatics()
	{
         // This creates the definitions that we need so we can use it in the calculation

		//Source captures
		OutgoingBaseDamageDef = FGameplayEffectAttributeCaptureDefinition(UAresDamageSet::GetOutgoingBaseDamageAttribute(), EGameplayEffectAttributeCaptureSource::Source, true);
		OutgoingAbilityDamageDef = FGameplayEffectAttributeCaptureDefinition(UAresDamageSet::GetOutgoingAbilityDamageAttribute(), EGameplayEffectAttributeCaptureSource::Source, true);

		//Target captures
		DamageDef = FGameplayEffectAttributeCaptureDefinition(UAresHealthSet::GetDamageAttribute(), EGameplayEffectAttributeCaptureSource::Target, false);
    }
};

//A static so we can use the above struct without needing to make a new instance for it.
static FAresDamageStatics& AresDamageStatics()
{
	static FAresDamageStatics Statics;
	return Statics;
}

UAresDamageFormulaExecution::UAresDamageFormulaExecution()
{
    /*
     * Exposes some modifiers to the calculation in the GE Blueprint.
    */
	//Source Captures
	RelevantAttributesToCapture.Add(AresDamageStatics().OutgoingBaseDamageDef);
	RelevantAttributesToCapture.Add(AresDamageStatics().OutgoingAbilityDamageDef);
}

void UAresDamageFormulaExecution::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
    //Cache the spec
	const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();

    //Grab the Effect Context
    FGameplayEffectContext* Context = Spec.GetContext()

    //Grab the ability that initiated this execution (can be nullptr!!)
	const UAresGameplayAbility* GameplayAbility = Cast<UAresGameplayAbility>(Context->GetAbilityInstance_NotReplicated());

	//Grab the tags from source and target
	const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
	const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();

	//Setup our evaluate params
	FAggregatorEvaluateParameters EvaluateParameters;
	EvaluateParameters.SourceTags = SourceTags;
	EvaluateParameters.TargetTags = TargetTags;

    //Grab the outgoing base damage from the source
	float OutgoingBaseDamage = 0.0f;
	ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(AresDamageStatics().OutgoingBaseDamageDef, EvaluateParameters, OutgoingBaseDamage);


    //Calculate the actual ability damage
	float AbilityDamage = 0.0f;
    ExecutionParams.AttemptCalculateCapturedAttributeMagnitudeWithBase(AresDamageStatics().OutgoingAbilityDamageDef, EvaluateParameters, BaseGameplayAbility ? GameplayAbility->GetDamageValue() : 0.f, AbilityDamage);
    
    //Our final damage value.
    float DamageDone = OutgoingBaseDamage + AbilityDamage;

    //If DamageDone is > 0, then we will do damage to the target, so we can go and set the output modifier.
    if (DamageDone > 0.0f)
	{
		
		OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(UAresHealthSet::GetDamageAttribute(), EGameplayModOp::Additive, DamageDone));
		
		// If we do any damage, then any conditional GEs are allowed to fire (once we return)
		OutExecutionOutput.MarkConditionalGameplayEffectsToTrigger();
	}
}

That is a wrap!

Hopefully this gives you a bit of insight into Execution Calculations and how to use them.
If you have any issues, check About Me to find out how to contact me!