235 lines
8.0 KiB
C++
235 lines
8.0 KiB
C++
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||
|
|
||
|
#include "LyraHealthSet.h"
|
||
|
#include "AbilitySystem/Attributes/LyraAttributeSet.h"
|
||
|
#include "LyraGameplayTags.h"
|
||
|
#include "Net/UnrealNetwork.h"
|
||
|
#include "AbilitySystem/LyraAbilitySystemComponent.h"
|
||
|
#include "Engine/World.h"
|
||
|
#include "GameplayEffectExtension.h"
|
||
|
#include "Messages/LyraVerbMessage.h"
|
||
|
#include "GameFramework/GameplayMessageSubsystem.h"
|
||
|
|
||
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraHealthSet)
|
||
|
|
||
|
UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_Damage, "Gameplay.Damage");
|
||
|
UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_DamageImmunity, "Gameplay.DamageImmunity");
|
||
|
UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_DamageSelfDestruct, "Gameplay.Damage.SelfDestruct");
|
||
|
UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_FellOutOfWorld, "Gameplay.Damage.FellOutOfWorld");
|
||
|
UE_DEFINE_GAMEPLAY_TAG(TAG_Lyra_Damage_Message, "Lyra.Damage.Message");
|
||
|
|
||
|
ULyraHealthSet::ULyraHealthSet()
|
||
|
: Health(100.0f)
|
||
|
, MaxHealth(100.0f)
|
||
|
{
|
||
|
bOutOfHealth = false;
|
||
|
MaxHealthBeforeAttributeChange = 0.0f;
|
||
|
HealthBeforeAttributeChange = 0.0f;
|
||
|
}
|
||
|
|
||
|
void ULyraHealthSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||
|
{
|
||
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||
|
|
||
|
DOREPLIFETIME_CONDITION_NOTIFY(ULyraHealthSet, Health, COND_None, REPNOTIFY_Always);
|
||
|
DOREPLIFETIME_CONDITION_NOTIFY(ULyraHealthSet, MaxHealth, COND_None, REPNOTIFY_Always);
|
||
|
}
|
||
|
|
||
|
void ULyraHealthSet::OnRep_Health(const FGameplayAttributeData& OldValue)
|
||
|
{
|
||
|
GAMEPLAYATTRIBUTE_REPNOTIFY(ULyraHealthSet, Health, OldValue);
|
||
|
|
||
|
// Call the change callback, but without an instigator
|
||
|
// This could be changed to an explicit RPC in the future
|
||
|
// These events on the client should not be changing attributes
|
||
|
|
||
|
const float CurrentHealth = GetHealth();
|
||
|
const float EstimatedMagnitude = CurrentHealth - OldValue.GetCurrentValue();
|
||
|
|
||
|
OnHealthChanged.Broadcast(nullptr, nullptr, nullptr, EstimatedMagnitude, OldValue.GetCurrentValue(), CurrentHealth);
|
||
|
|
||
|
if (!bOutOfHealth && CurrentHealth <= 0.0f)
|
||
|
{
|
||
|
OnOutOfHealth.Broadcast(nullptr, nullptr, nullptr, EstimatedMagnitude, OldValue.GetCurrentValue(), CurrentHealth);
|
||
|
}
|
||
|
|
||
|
bOutOfHealth = (CurrentHealth <= 0.0f);
|
||
|
}
|
||
|
|
||
|
void ULyraHealthSet::OnRep_MaxHealth(const FGameplayAttributeData& OldValue)
|
||
|
{
|
||
|
GAMEPLAYATTRIBUTE_REPNOTIFY(ULyraHealthSet, MaxHealth, OldValue);
|
||
|
|
||
|
// Call the change callback, but without an instigator
|
||
|
// This could be changed to an explicit RPC in the future
|
||
|
OnMaxHealthChanged.Broadcast(nullptr, nullptr, nullptr, GetMaxHealth() - OldValue.GetCurrentValue(), OldValue.GetCurrentValue(), GetMaxHealth());
|
||
|
}
|
||
|
|
||
|
bool ULyraHealthSet::PreGameplayEffectExecute(FGameplayEffectModCallbackData &Data)
|
||
|
{
|
||
|
if (!Super::PreGameplayEffectExecute(Data))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Handle modifying incoming normal damage
|
||
|
if (Data.EvaluatedData.Attribute == GetDamageAttribute())
|
||
|
{
|
||
|
if (Data.EvaluatedData.Magnitude > 0.0f)
|
||
|
{
|
||
|
const bool bIsDamageFromSelfDestruct = Data.EffectSpec.GetDynamicAssetTags().HasTagExact(TAG_Gameplay_DamageSelfDestruct);
|
||
|
|
||
|
if (Data.Target.HasMatchingGameplayTag(TAG_Gameplay_DamageImmunity) && !bIsDamageFromSelfDestruct)
|
||
|
{
|
||
|
// Do not take away any health.
|
||
|
Data.EvaluatedData.Magnitude = 0.0f;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
#if !UE_BUILD_SHIPPING
|
||
|
// Check GodMode cheat, unlimited health is checked below
|
||
|
if (Data.Target.HasMatchingGameplayTag(LyraGameplayTags::Cheat_GodMode) && !bIsDamageFromSelfDestruct)
|
||
|
{
|
||
|
// Do not take away any health.
|
||
|
Data.EvaluatedData.Magnitude = 0.0f;
|
||
|
return false;
|
||
|
}
|
||
|
#endif // #if !UE_BUILD_SHIPPING
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Save the current health
|
||
|
HealthBeforeAttributeChange = GetHealth();
|
||
|
MaxHealthBeforeAttributeChange = GetMaxHealth();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void ULyraHealthSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
|
||
|
{
|
||
|
Super::PostGameplayEffectExecute(Data);
|
||
|
|
||
|
const bool bIsDamageFromSelfDestruct = Data.EffectSpec.GetDynamicAssetTags().HasTagExact(TAG_Gameplay_DamageSelfDestruct);
|
||
|
float MinimumHealth = 0.0f;
|
||
|
|
||
|
#if !UE_BUILD_SHIPPING
|
||
|
// Godmode and unlimited health stop death unless it's a self destruct
|
||
|
if (!bIsDamageFromSelfDestruct &&
|
||
|
(Data.Target.HasMatchingGameplayTag(LyraGameplayTags::Cheat_GodMode) || Data.Target.HasMatchingGameplayTag(LyraGameplayTags::Cheat_UnlimitedHealth) ))
|
||
|
{
|
||
|
MinimumHealth = 1.0f;
|
||
|
}
|
||
|
#endif // #if !UE_BUILD_SHIPPING
|
||
|
|
||
|
const FGameplayEffectContextHandle& EffectContext = Data.EffectSpec.GetEffectContext();
|
||
|
AActor* Instigator = EffectContext.GetOriginalInstigator();
|
||
|
AActor* Causer = EffectContext.GetEffectCauser();
|
||
|
|
||
|
if (Data.EvaluatedData.Attribute == GetDamageAttribute())
|
||
|
{
|
||
|
// Send a standardized verb message that other systems can observe
|
||
|
if (Data.EvaluatedData.Magnitude > 0.0f)
|
||
|
{
|
||
|
FLyraVerbMessage Message;
|
||
|
Message.Verb = TAG_Lyra_Damage_Message;
|
||
|
Message.Instigator = Data.EffectSpec.GetEffectContext().GetEffectCauser();
|
||
|
Message.InstigatorTags = *Data.EffectSpec.CapturedSourceTags.GetAggregatedTags();
|
||
|
Message.Target = GetOwningActor();
|
||
|
Message.TargetTags = *Data.EffectSpec.CapturedTargetTags.GetAggregatedTags();
|
||
|
//@TODO: Fill out context tags, and any non-ability-system source/instigator tags
|
||
|
//@TODO: Determine if it's an opposing team kill, self-own, team kill, etc...
|
||
|
Message.Magnitude = Data.EvaluatedData.Magnitude;
|
||
|
|
||
|
UGameplayMessageSubsystem& MessageSystem = UGameplayMessageSubsystem::Get(GetWorld());
|
||
|
MessageSystem.BroadcastMessage(Message.Verb, Message);
|
||
|
}
|
||
|
|
||
|
// Convert into -Health and then clamp
|
||
|
SetHealth(FMath::Clamp(GetHealth() - GetDamage(), MinimumHealth, GetMaxHealth()));
|
||
|
SetDamage(0.0f);
|
||
|
}
|
||
|
else if (Data.EvaluatedData.Attribute == GetHealingAttribute())
|
||
|
{
|
||
|
// Convert into +Health and then clamo
|
||
|
SetHealth(FMath::Clamp(GetHealth() + GetHealing(), MinimumHealth, GetMaxHealth()));
|
||
|
SetHealing(0.0f);
|
||
|
}
|
||
|
else if (Data.EvaluatedData.Attribute == GetHealthAttribute())
|
||
|
{
|
||
|
// Clamp and fall into out of health handling below
|
||
|
SetHealth(FMath::Clamp(GetHealth(), MinimumHealth, GetMaxHealth()));
|
||
|
}
|
||
|
else if (Data.EvaluatedData.Attribute == GetMaxHealthAttribute())
|
||
|
{
|
||
|
// TODO clamp current health?
|
||
|
|
||
|
// Notify on any requested max health changes
|
||
|
OnMaxHealthChanged.Broadcast(Instigator, Causer, &Data.EffectSpec, Data.EvaluatedData.Magnitude, MaxHealthBeforeAttributeChange, GetMaxHealth());
|
||
|
}
|
||
|
|
||
|
// If health has actually changed activate callbacks
|
||
|
if (GetHealth() != HealthBeforeAttributeChange)
|
||
|
{
|
||
|
OnHealthChanged.Broadcast(Instigator, Causer, &Data.EffectSpec, Data.EvaluatedData.Magnitude, HealthBeforeAttributeChange, GetHealth());
|
||
|
}
|
||
|
|
||
|
if ((GetHealth() <= 0.0f) && !bOutOfHealth)
|
||
|
{
|
||
|
OnOutOfHealth.Broadcast(Instigator, Causer, &Data.EffectSpec, Data.EvaluatedData.Magnitude, HealthBeforeAttributeChange, GetHealth());
|
||
|
}
|
||
|
|
||
|
// Check health again in case an event above changed it.
|
||
|
bOutOfHealth = (GetHealth() <= 0.0f);
|
||
|
}
|
||
|
|
||
|
void ULyraHealthSet::PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const
|
||
|
{
|
||
|
Super::PreAttributeBaseChange(Attribute, NewValue);
|
||
|
|
||
|
ClampAttribute(Attribute, NewValue);
|
||
|
}
|
||
|
|
||
|
void ULyraHealthSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
|
||
|
{
|
||
|
Super::PreAttributeChange(Attribute, NewValue);
|
||
|
|
||
|
ClampAttribute(Attribute, NewValue);
|
||
|
}
|
||
|
|
||
|
void ULyraHealthSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
|
||
|
{
|
||
|
Super::PostAttributeChange(Attribute, OldValue, NewValue);
|
||
|
|
||
|
if (Attribute == GetMaxHealthAttribute())
|
||
|
{
|
||
|
// Make sure current health is not greater than the new max health.
|
||
|
if (GetHealth() > NewValue)
|
||
|
{
|
||
|
ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent();
|
||
|
check(LyraASC);
|
||
|
|
||
|
LyraASC->ApplyModToAttribute(GetHealthAttribute(), EGameplayModOp::Override, NewValue);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bOutOfHealth && (GetHealth() > 0.0f))
|
||
|
{
|
||
|
bOutOfHealth = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ULyraHealthSet::ClampAttribute(const FGameplayAttribute& Attribute, float& NewValue) const
|
||
|
{
|
||
|
if (Attribute == GetHealthAttribute())
|
||
|
{
|
||
|
// Do not allow health to go negative or above max health.
|
||
|
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
|
||
|
}
|
||
|
else if (Attribute == GetMaxHealthAttribute())
|
||
|
{
|
||
|
// Do not allow max health to drop below 1.
|
||
|
NewValue = FMath::Max(NewValue, 1.0f);
|
||
|
}
|
||
|
}
|
||
|
|