Changing "Source" and "Target" attributes with Execution Calculation in Gameplay Ability System

Hi All,
I’m trying to implement a Life Steal Gameplay Effect Exec Class. In this I need to modify the Attributes of Source as well as Target. My problem is that the attribute of Target is changed correctly, however, the attribute of Source does not change. Although both the attributes are captured properly but the changes only apply to Target and not the source.

My code:

GEExecLifeSteal.h

#pragma once

#include "CoreMinimal.h"
#include "GameplayEffectExecutionCalculation.h"
#include "GEExecLifeSteal.generated.h"

/**
 * 
 */
UCLASS()
class GASREP_API UGEExecLifeSteal : public UGameplayEffectExecutionCalculation
{
	GENERATED_BODY()

public:
	UGEExecLifeSteal();

	void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecParams, FGameplayEffectCustomExecutionOutput& ExecOutputs) const;
	
};

AND

GEExecLifeSteal.cpp

#include "GEExecLifeSteal.h"
#include "GASAttributeSet.h"
#include "AbilitySystemComponent.h"

struct SourceHealthCapture
{
	DECLARE_ATTRIBUTE_CAPTUREDEF(Health)

	SourceHealthCapture()
	{
		DEFINE_ATTRIBUTE_CAPTUREDEF(UGASAttributeSet, Health, Source, true)
	}
};

struct TargetHealthCapture
{
	DECLARE_ATTRIBUTE_CAPTUREDEF(Health)

	TargetHealthCapture()
	{
		DEFINE_ATTRIBUTE_CAPTUREDEF(UGASAttributeSet, Health, Target, true)
	}
};

static SourceHealthCapture& GetSourceHealthCapture()
{
	static SourceHealthCapture SourceHealthCaptureVar;
	return SourceHealthCaptureVar;
}

static TargetHealthCapture& GetTargetHealthCapture()
{
	static TargetHealthCapture TargetHealthCaptureVar;
	return TargetHealthCaptureVar;
}

UGEExecLifeSteal::UGEExecLifeSteal()
{
	RelevantAttributesToCapture.Add(GetSourceHealthCapture().HealthDef);
	RelevantAttributesToCapture.Add(GetTargetHealthCapture().HealthDef);
	//Remember to add this tag to your project
	ValidTransientAggregatorIdentifiers.AddTag(FGameplayTag::RequestGameplayTag("char.calc.damage.extra"));
}

void UGEExecLifeSteal::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecParams, FGameplayEffectCustomExecutionOutput& ExecOutputs) const
{
	//Get ASC
	UAbilitySystemComponent* TargetAbilitySystemComponent = ExecParams.GetTargetAbilitySystemComponent();
	UAbilitySystemComponent* SourceAbilitySystemComponent = ExecParams.GetSourceAbilitySystemComponent();

	//Calc Variables for outputting final values
	float OutSourceHealth = 0.0f;
	float OutTargetHealth = 0.0f;

	//Variables to store captured values
	float SourceHealthMagnitude = 0.0f;
	float TargetHealthMagnitude = 0.0f;
	float ExtraAttackDamage = 0.0f;

	ExecParams.AttemptCalculateCapturedAttributeMagnitude(GetSourceHealthCapture().HealthDef, FAggregatorEvaluateParameters(), SourceHealthMagnitude);
	ExecParams.AttemptCalculateCapturedAttributeMagnitude(GetTargetHealthCapture().HealthDef, FAggregatorEvaluateParameters(), TargetHealthMagnitude);
	ExecParams.AttemptCalculateTransientAggregatorMagnitude(FGameplayTag::RequestGameplayTag("char.calc.damage.extra"), FAggregatorEvaluateParameters(), ExtraAttackDamage);

	//Start the Calculation
	OutSourceHealth = SourceHealthMagnitude + (ExtraAttackDamage * 0.5);
	OutTargetHealth = TargetHealthMagnitude - ExtraAttackDamage;

	
	FString TargetActorLabel = TargetAbilitySystemComponent->GetOwnerActor()->GetActorLabel();
	FString SourceActorLabel = SourceAbilitySystemComponent->GetOwnerActor()->GetActorLabel();
	
	ExecOutputs.AddOutputModifier(FGameplayModifierEvaluatedData(GetSourceHealthCapture().HealthProperty, EGameplayModOp::Override, OutSourceHealth));
	ExecOutputs.AddOutputModifier(FGameplayModifierEvaluatedData(GetTargetHealthCapture().HealthProperty, EGameplayModOp::Override, OutTargetHealth));
	UE_LOG(LogTemp, Warning, TEXT("TA=%s, SA=%s, OutSourceHealth=%f, SourceHealthMagnitude=%f, OutTargetHealth=%f, TargetHealthMagnitude=%f"),
		*TargetActorLabel, *SourceActorLabel, OutSourceHealth, SourceHealthMagnitude, OutTargetHealth, TargetHealthMagnitude);
}

1 Like

In the end did you succeed? I have the same kind of usage !

That’s because the flow is this →
ApplyGameplayEffectToTarget ( target ) ← calculate damage and apply it to target attributes

however what you are trying to implement would be →
ApplyGameplayEffectToTargetAndSource(target, source)

but the effects always are outputted to target

I implemented lifesteal with a “ApplyModToAttribute” which is essentially like a gameplay effect without calling the “Pre/PostGameplayEffectExecution”. I also have PreAttributeChange set up to clamp health just in case.

float Lifesteal = 100.f; // Some value

UAbilitySystemComponent* SourceABSC = ExecutionParams.GetSourceAbilitySystemComponent();

SourceABSC->ApplyModToAttribute(UAttributeSet::GetHealthAttribute(), EGameplayModOp::Additive, LifeSteal);

What I am thinking is whether this is the correct way to do it.
I’m thinking of:

OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(UAttributeSet::GetLeechAttribute(), EGameplayModOp::Additive, LifeSteal));

...

 if(Data.EvaluatedData.Attribute == GetLeechAttribute())
{
  SourceAttributeSet->FMath::Clamp(SetHealth(GetHealht() + GetLeech()), 0.f, GetMaxHealth());
}

However I don’t know how and where to get the reference to “SourceAttributeSet”

I did figure it out how to to the GetAttributeSet way.
In PostGameplayEffectExecute(DATA):

// YourAttributeSet.cpp

if(Data.EvaluatedData.Attribute == GetLeechAttribute())
{

const FGameplayEffectContextHandle& EffectContext = Data.EffectSpec.GetEffectContext();

UAbilitySystemComponent* SourceASC = EffectContext.GetOriginalInstigatorAbilitySystemComponent();

    const UAttributeSet* SourceAttributeSet = SourceASC->GetAttributeSet(this->GetClass());
    if(UYourAttributeSet * AsSourceAttributeSet = const_cast<UYourAttributeSet*>(Cast<UYourAttributeSet>(SourceAttributeSet))){
    float SourceHealth = AsSourceAttributeSet->GetHealth();
    float SourceMaxHealth = AsSourceAttributeSet->GetMaxHealth();
    
// Set The Health of Gameplay Effect Source
    AsSourceAttributeSet->SetHealth(FMath::Clamp(SourceHealth + GetLeech(), 0.0f, SourceMaxHealth));
  }
}

or
in the execution.cpp

float LifeRestored = 100.f;
if(LifeRestored > 0.0f && TargetActor != SourceActor)
{
  const UAttributeSet* SourceAttributes = SourceABSC->GetAttributeSet(UYourAttributeSet::StaticClass());
		if(UBaseAttributeSet* BAS = const_cast<UYourAttributeSet*>(Cast<UYourAttributeSet>(SourceAttributes))){
			float SourceHealth = BAS->GetHealth();
			float SourceMaxHealth = BAS->GetMaxHealth();
			BAS->SetHealth(FMath::Clamp(SourceHealth + LifeRestored, 0.0f, SourceMaxHealth));
		}
}


1 Like

Perhaps I will bump this topic, since we have not received a professional answer.

Probably this way is the best: