Created
March 13, 2022 11:14
-
-
Save mklabs/fcf8b3c355fdc43b3738957687b492e7 to your computer and use it in GitHub Desktop.
Example of Gameplay Effect Execution Calculation with logic to handle health and stamina damage if blocking, and dealing with parry frames which nullifies damage and sends an event to trigger a "parried" ability on source character.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2020 Mickael Daniel. | |
#include "Abilities/RPGDamageExecution.h" | |
#include "AbilitySystemBlueprintLibrary.h" | |
#include "RPGBlueprintLibrary.h" | |
#include "Abilities/RPGAttributeSet.h" | |
#include "Characters/RPGCharacterBase.h" | |
#include "Items/RPGWeaponActor.h" | |
struct FRPGDamageStatics | |
{ | |
DECLARE_ATTRIBUTE_CAPTUREDEF(Damage); | |
DECLARE_ATTRIBUTE_CAPTUREDEF(StaminaDamage); | |
FRPGDamageStatics() | |
{ | |
// Capture the Target's DefensePower attribute. Do not snapshot it, because we want to use the health value at the moment we apply the execution. | |
// DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, DefensePower, Target, false); | |
// Capture the Source's AttackPower. We do want to snapshot this at the moment we create the GameplayEffectSpec that will execute the damage. | |
// (imagine we fire a projectile: we create the GE Spec when the projectile is fired. When it hits the target, we want to use the AttackPower at the moment | |
// the projectile was launched, not when it hits). | |
// DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, AttackPower, Source, true); | |
// Also capture the source's raw Damage, which is normally passed in directly via the execution | |
DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, StaminaDamage, Source, true); | |
DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, Damage, Source, true); | |
} | |
}; | |
static const FRPGDamageStatics& DamageStatics() | |
{ | |
static FRPGDamageStatics DamageStatics; | |
return DamageStatics; | |
} | |
URPGDamageExecution::URPGDamageExecution() | |
{ | |
VulnerableAbilityTag = FGameplayTag::RequestGameplayTag(FName("Event.Ability.Vulnerable")); | |
RelevantAttributesToCapture.Add(DamageStatics().DamageDef); | |
RelevantAttributesToCapture.Add(DamageStatics().StaminaDamageDef); | |
} | |
void URPGDamageExecution::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const | |
{ | |
UAbilitySystemComponent* TargetAbilitySystemComponent = ExecutionParams.GetTargetAbilitySystemComponent(); | |
UAbilitySystemComponent* SourceAbilitySystemComponent = ExecutionParams.GetSourceAbilitySystemComponent(); | |
AActor* SourceActor = SourceAbilitySystemComponent ? SourceAbilitySystemComponent->GetAvatarActor() : nullptr; | |
AActor* TargetActor = TargetAbilitySystemComponent ? TargetAbilitySystemComponent->GetAvatarActor() : nullptr; | |
ARPGCharacterBase* SourceCharacter = Cast<ARPGCharacterBase>(SourceActor); | |
ARPGCharacterBase* TargetCharacter = Cast<ARPGCharacterBase>(TargetActor); | |
const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec(); | |
// Gather the tags from the source and target as that can affect which buffs should be used | |
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags(); | |
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags(); | |
FAggregatorEvaluateParameters EvaluationParameters; | |
EvaluationParameters.SourceTags = SourceTags; | |
EvaluationParameters.TargetTags = TargetTags; | |
float AttackPower = 1.0f; | |
float Stability = 0.0f; | |
float GuardAbsorption = 0.0f; | |
// TODO: Better way to figure out from which weapon to get Attack Power and Stability, | |
// For now right left weapon if equipped takes precedence over right weapon for Defense | |
// when guarding (Stability, GuardAbsorption) | |
// | |
// TODO: Handle types of Attack (Physical, Magic, Fire, ...) | |
if (SourceCharacter && SourceCharacter->GetRightEquippedWeapon()) | |
{ | |
AttackPower = SourceCharacter->GetRightEquippedWeapon()->WeaponAttributes->AttackPowerPhysical; | |
} | |
if (TargetCharacter) | |
{ | |
if (TargetCharacter->GetRightEquippedWeapon()) | |
{ | |
Stability = TargetCharacter->GetRightEquippedWeapon()->WeaponAttributes->Stability; | |
GuardAbsorption = TargetCharacter->GetRightEquippedWeapon()->WeaponAttributes->GuardAbsorptionPhysical; | |
} | |
if (TargetCharacter->GetLeftEquippedWeapon()) | |
{ | |
Stability = TargetCharacter->GetLeftEquippedWeapon()->WeaponAttributes->Stability; | |
GuardAbsorption = TargetCharacter->GetLeftEquippedWeapon()->WeaponAttributes->GuardAbsorptionPhysical; | |
} | |
} | |
// -------------------------------------- | |
// Damage Done = (Damage * AttackPower ) * (1 - (GuardAbsorption / 100) | |
// If DefensePower is 0, it is treated as 1.0 | |
// -------------------------------------- | |
float Damage = 0.0f; | |
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().DamageDef, EvaluationParameters, Damage); | |
float DamageDone = Damage * AttackPower; | |
// -------------------------------------- | |
// Stamina Damage Done = (StaminaDamage * AttackPower) * (1 - (Stability / 100)) | |
// If Stability is 0, Stamina Damage is now reduced and takes full effect | |
// -------------------------------------- | |
float StaminaDamage = 0.0f; | |
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().StaminaDamageDef, EvaluationParameters, StaminaDamage); | |
float StaminaDamageDone = 0.0f; | |
// Only apply StaminaDamage and reduce Damage by GuardAbsorption if the target is blocking | |
if (TargetTags->HasTagExact(FGameplayTag::RequestGameplayTag("State.Blocking"))) | |
{ | |
// And if the Source Actor is attacking from the front | |
const float Angle = URPGBlueprintLibrary::FindDegreeToTarget(TargetActor, SourceActor); | |
if (FMath::Abs(Angle) < 90.0f) | |
{ | |
StaminaDamageDone = (StaminaDamage * AttackPower) * (1 - (Stability / 100)); | |
DamageDone = DamageDone * (1 - (GuardAbsorption / 100)); | |
} | |
} | |
// Handle Parry Frames if active on target | |
if (TargetTags->HasTag(FGameplayTag::RequestGameplayTag("State.Parry"))) | |
{ | |
if (TargetTags->HasTagExact(FGameplayTag::RequestGameplayTag("State.Parry.Partial"))) | |
{ | |
DamageDone = DamageDone * 0.25f; | |
} | |
if (TargetTags->HasTagExact(FGameplayTag::RequestGameplayTag("State.Parry.Perfect"))) | |
{ | |
DamageDone = 0.0f; | |
} | |
// Trigger back vulnerable ability to Source Character via Event | |
FGameplayEventData Payload; | |
SourceCharacter->ReplicatedSendGameplayEvent(VulnerableAbilityTag, Payload); | |
} | |
if (DamageDone > 0.0f) | |
{ | |
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(DamageStatics().DamageProperty, EGameplayModOp::Additive, DamageDone)); | |
} | |
if (StaminaDamageDone > 0.0f) | |
{ | |
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(DamageStatics().StaminaDamageProperty, EGameplayModOp::Additive, StaminaDamageDone)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2020 Mickael Daniel. | |
#pragma once | |
#include "CoreMinimal.h" | |
#include "GameplayEffectExecutionCalculation.h" | |
#include "RPGGameplayAbility.h" | |
#include "RPGDamageExecution.generated.h" | |
/** | |
* A damage execution, which allows doing damage by combining a raw Damage Number with AttackPower and DefensePower. | |
* | |
* It also checks target state via tags to modify the applied damage, which can be done to Health or Stamina. | |
*/ | |
UCLASS() | |
class ARPG_API URPGDamageExecution : public UGameplayEffectExecutionCalculation | |
{ | |
GENERATED_BODY() | |
public: | |
URPGDamageExecution(); | |
virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override; | |
private: | |
/** Cached Gameplay Tags */ | |
FGameplayTag VulnerableAbilityTag; | |
}; |
Hello. How would you change the execution?
@Valera94 At first glance, I would say:
- Use of native gameplay tags instead of FGameplayTag::RequestGameplayTag
- Split each part of the executions in its own method (Equipment part, Blocking part, Parry part)
- DamageStatics should be named
FDamageStatics
- Rework DamageStatics part and avoid multiple invocation / allocation. It can be done only once.
This code is pretty old, I think it was implemented in 4.26 or 4.27.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I would probably do that a bit differently now, but that's the gist of it.