// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraHealthSet.h" #include "LyraGameplayTags.h" #include "Net/UnrealNetwork.h" #include "AbilitySystem/LyraAbilitySystemComponent.h" #include "GameplayEffectExtension.h" #include "GameplayEffectTypes.h" #include "Messages/LyraVerbMessage.h" #include "GameFramework/GameplayMessageSubsystem.h" 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; } void ULyraHealthSet::GetLifetimeReplicatedProps(TArray& 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); } void ULyraHealthSet::OnRep_MaxHealth(const FGameplayAttributeData& OldValue) { GAMEPLAYATTRIBUTE_REPNOTIFY(ULyraHealthSet, MaxHealth, OldValue); } bool ULyraHealthSet::PreGameplayEffectExecute(FGameplayEffectModCallbackData &Data) { if (!Super::PreGameplayEffectExecute(Data)) { return false; } if (Data.EvaluatedData.Attribute == GetHealthAttribute()) { 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. if (Data.Target.HasMatchingGameplayTag(FLyraGameplayTags::Get().Cheat_GodMode) && !bIsDamageFromSelfDestruct) { // Do not take away any health. Data.EvaluatedData.Magnitude = 0.0f; return false; } // Check UnlimitedHealth cheat. if (Data.Target.HasMatchingGameplayTag(FLyraGameplayTags::Get().Cheat_UnlimitedHealth) && !bIsDamageFromSelfDestruct) { // Do not drop below 1 health. Data.EvaluatedData.Magnitude = FMath::Max(Data.EvaluatedData.Magnitude, (-GetHealth() + 1.0f)); } #endif // #if !UE_BUILD_SHIPPING } } return true; } void ULyraHealthSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) { Super::PostGameplayEffectExecute(Data); if (Data.EvaluatedData.Attribute == GetHealthAttribute()) { // 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); } if ((GetHealth() <= 0.0f) && !bOutOfHealth) { if (OnOutOfHealth.IsBound()) { const FGameplayEffectContextHandle& EffectContext = Data.EffectSpec.GetEffectContext(); AActor* Instigator = EffectContext.GetOriginalInstigator(); AActor* Causer = EffectContext.GetEffectCauser(); OnOutOfHealth.Broadcast(Instigator, Causer, Data.EffectSpec, Data.EvaluatedData.Magnitude); } } // Check health again in case an event above changed it. bOutOfHealth = (GetHealth() <= 0.0f); } else if (Data.EvaluatedData.Attribute == GetHealingAttribute()) { SetHealth(GetHealth() + GetHealing()); SetHealing(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); } }