2022-05-23 18:41:30 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
2022-09-13 07:18:28 +00:00
# include "Character/LyraHealthComponent.h"
2022-05-23 18:41:30 +00:00
# include "LyraLogChannels.h"
# include "System/LyraAssetManager.h"
# include "System/LyraGameData.h"
# include "LyraGameplayTags.h"
# include "Net/UnrealNetwork.h"
# include "GameplayEffect.h"
# include "GameplayEffectExtension.h"
# include "GameplayPrediction.h"
# include "Abilities/GameplayAbilityTypes.h"
# include "AbilitySystem/LyraAbilitySystemComponent.h"
# include "AbilitySystem/Attributes/LyraHealthSet.h"
# include "Messages/LyraVerbMessage.h"
# include "Messages/LyraVerbMessageHelpers.h"
# include "NativeGameplayTags.h"
# include "Components/GameFrameworkComponentManager.h"
# include "GameFramework/GameplayMessageSubsystem.h"
# include "GameFramework/PlayerState.h"
2022-09-13 07:18:28 +00:00
# include "Engine/World.h"
2022-05-23 18:41:30 +00:00
UE_DEFINE_GAMEPLAY_TAG_STATIC ( TAG_Lyra_Elimination_Message , " Lyra.Elimination.Message " ) ;
ULyraHealthComponent : : ULyraHealthComponent ( const FObjectInitializer & ObjectInitializer )
: Super ( ObjectInitializer )
{
PrimaryComponentTick . bStartWithTickEnabled = false ;
PrimaryComponentTick . bCanEverTick = false ;
SetIsReplicatedByDefault ( true ) ;
AbilitySystemComponent = nullptr ;
HealthSet = nullptr ;
DeathState = ELyraDeathState : : NotDead ;
}
void ULyraHealthComponent : : GetLifetimeReplicatedProps ( TArray < FLifetimeProperty > & OutLifetimeProps ) const
{
Super : : GetLifetimeReplicatedProps ( OutLifetimeProps ) ;
DOREPLIFETIME ( ULyraHealthComponent , DeathState ) ;
}
void ULyraHealthComponent : : OnUnregister ( )
{
UninitializeFromAbilitySystem ( ) ;
Super : : OnUnregister ( ) ;
}
void ULyraHealthComponent : : InitializeWithAbilitySystem ( ULyraAbilitySystemComponent * InASC )
{
AActor * Owner = GetOwner ( ) ;
check ( Owner ) ;
if ( AbilitySystemComponent )
{
UE_LOG ( LogLyra , Error , TEXT ( " LyraHealthComponent: Health component for owner [%s] has already been initialized with an ability system. " ) , * GetNameSafe ( Owner ) ) ;
return ;
}
AbilitySystemComponent = InASC ;
if ( ! AbilitySystemComponent )
{
UE_LOG ( LogLyra , Error , TEXT ( " LyraHealthComponent: Cannot initialize health component for owner [%s] with NULL ability system. " ) , * GetNameSafe ( Owner ) ) ;
return ;
}
HealthSet = AbilitySystemComponent - > GetSet < ULyraHealthSet > ( ) ;
if ( ! HealthSet )
{
UE_LOG ( LogLyra , Error , TEXT ( " LyraHealthComponent: Cannot initialize health component for owner [%s] with NULL health set on the ability system. " ) , * GetNameSafe ( Owner ) ) ;
return ;
}
// Register to listen for attribute changes.
AbilitySystemComponent - > GetGameplayAttributeValueChangeDelegate ( ULyraHealthSet : : GetHealthAttribute ( ) ) . AddUObject ( this , & ThisClass : : HandleHealthChanged ) ;
AbilitySystemComponent - > GetGameplayAttributeValueChangeDelegate ( ULyraHealthSet : : GetMaxHealthAttribute ( ) ) . AddUObject ( this , & ThisClass : : HandleMaxHealthChanged ) ;
HealthSet - > OnOutOfHealth . AddUObject ( this , & ThisClass : : HandleOutOfHealth ) ;
// TEMP: Reset attributes to default values. Eventually this will be driven by a spread sheet.
AbilitySystemComponent - > SetNumericAttributeBase ( ULyraHealthSet : : GetHealthAttribute ( ) , HealthSet - > GetMaxHealth ( ) ) ;
ClearGameplayTags ( ) ;
OnHealthChanged . Broadcast ( this , HealthSet - > GetHealth ( ) , HealthSet - > GetHealth ( ) , nullptr ) ;
OnMaxHealthChanged . Broadcast ( this , HealthSet - > GetHealth ( ) , HealthSet - > GetHealth ( ) , nullptr ) ;
//UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(GetOwner(), UGameFrameworkComponentManager::NAME_HealthComponentReady);
}
void ULyraHealthComponent : : UninitializeFromAbilitySystem ( )
{
ClearGameplayTags ( ) ;
if ( HealthSet )
{
HealthSet - > OnOutOfHealth . RemoveAll ( this ) ;
}
HealthSet = nullptr ;
AbilitySystemComponent = nullptr ;
}
void ULyraHealthComponent : : ClearGameplayTags ( )
{
if ( AbilitySystemComponent )
{
const FLyraGameplayTags & GameplayTags = FLyraGameplayTags : : Get ( ) ;
AbilitySystemComponent - > SetLooseGameplayTagCount ( GameplayTags . Status_Death_Dying , 0 ) ;
AbilitySystemComponent - > SetLooseGameplayTagCount ( GameplayTags . Status_Death_Dead , 0 ) ;
}
}
float ULyraHealthComponent : : GetHealth ( ) const
{
return ( HealthSet ? HealthSet - > GetHealth ( ) : 0.0f ) ;
}
float ULyraHealthComponent : : GetMaxHealth ( ) const
{
return ( HealthSet ? HealthSet - > GetMaxHealth ( ) : 0.0f ) ;
}
float ULyraHealthComponent : : GetHealthNormalized ( ) const
{
if ( HealthSet )
{
const float Health = HealthSet - > GetHealth ( ) ;
const float MaxHealth = HealthSet - > GetMaxHealth ( ) ;
return ( ( MaxHealth > 0.0f ) ? ( Health / MaxHealth ) : 0.0f ) ;
}
return 0.0f ;
}
static AActor * GetInstigatorFromAttrChangeData ( const FOnAttributeChangeData & ChangeData )
{
if ( ChangeData . GEModData ! = nullptr )
{
const FGameplayEffectContextHandle & EffectContext = ChangeData . GEModData - > EffectSpec . GetEffectContext ( ) ;
return EffectContext . GetOriginalInstigator ( ) ;
}
return nullptr ;
}
void ULyraHealthComponent : : HandleHealthChanged ( const FOnAttributeChangeData & ChangeData )
{
OnHealthChanged . Broadcast ( this , ChangeData . OldValue , ChangeData . NewValue , GetInstigatorFromAttrChangeData ( ChangeData ) ) ;
}
void ULyraHealthComponent : : HandleMaxHealthChanged ( const FOnAttributeChangeData & ChangeData )
{
OnMaxHealthChanged . Broadcast ( this , ChangeData . OldValue , ChangeData . NewValue , GetInstigatorFromAttrChangeData ( ChangeData ) ) ;
}
void ULyraHealthComponent : : HandleOutOfHealth ( AActor * DamageInstigator , AActor * DamageCauser , const FGameplayEffectSpec & DamageEffectSpec , float DamageMagnitude )
{
# if WITH_SERVER_CODE
if ( AbilitySystemComponent )
{
// Send the "GameplayEvent.Death" gameplay event through the owner's ability system. This can be used to trigger a death gameplay ability.
{
FGameplayEventData Payload ;
Payload . EventTag = FLyraGameplayTags : : Get ( ) . GameplayEvent_Death ;
Payload . Instigator = DamageInstigator ;
Payload . Target = AbilitySystemComponent - > GetAvatarActor ( ) ;
Payload . OptionalObject = DamageEffectSpec . Def ;
Payload . ContextHandle = DamageEffectSpec . GetEffectContext ( ) ;
Payload . InstigatorTags = * DamageEffectSpec . CapturedSourceTags . GetAggregatedTags ( ) ;
Payload . TargetTags = * DamageEffectSpec . CapturedTargetTags . GetAggregatedTags ( ) ;
Payload . EventMagnitude = DamageMagnitude ;
FScopedPredictionWindow NewScopedWindow ( AbilitySystemComponent , true ) ;
AbilitySystemComponent - > HandleGameplayEvent ( Payload . EventTag , & Payload ) ;
}
// Send a standardized verb message that other systems can observe
{
FLyraVerbMessage Message ;
Message . Verb = TAG_Lyra_Elimination_Message ;
Message . Instigator = DamageInstigator ;
Message . InstigatorTags = * DamageEffectSpec . CapturedSourceTags . GetAggregatedTags ( ) ;
Message . Target = ULyraVerbMessageHelpers : : GetPlayerStateFromObject ( AbilitySystemComponent - > GetAvatarActor ( ) ) ;
Message . TargetTags = * DamageEffectSpec . 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...
UGameplayMessageSubsystem & MessageSystem = UGameplayMessageSubsystem : : Get ( GetWorld ( ) ) ;
MessageSystem . BroadcastMessage ( Message . Verb , Message ) ;
}
//@TODO: assist messages (could compute from damage dealt elsewhere)?
}
# endif // #if WITH_SERVER_CODE
}
void ULyraHealthComponent : : OnRep_DeathState ( ELyraDeathState OldDeathState )
{
const ELyraDeathState NewDeathState = DeathState ;
// Revert the death state for now since we rely on StartDeath and FinishDeath to change it.
DeathState = OldDeathState ;
if ( OldDeathState > NewDeathState )
{
// The server is trying to set us back but we've already predicted past the server state.
UE_LOG ( LogLyra , Warning , TEXT ( " LyraHealthComponent: Predicted past server death state [%d] -> [%d] for owner [%s]. " ) , ( uint8 ) OldDeathState , ( uint8 ) NewDeathState , * GetNameSafe ( GetOwner ( ) ) ) ;
return ;
}
if ( OldDeathState = = ELyraDeathState : : NotDead )
{
if ( NewDeathState = = ELyraDeathState : : DeathStarted )
{
StartDeath ( ) ;
}
else if ( NewDeathState = = ELyraDeathState : : DeathFinished )
{
StartDeath ( ) ;
FinishDeath ( ) ;
}
else
{
UE_LOG ( LogLyra , Error , TEXT ( " LyraHealthComponent: Invalid death transition [%d] -> [%d] for owner [%s]. " ) , ( uint8 ) OldDeathState , ( uint8 ) NewDeathState , * GetNameSafe ( GetOwner ( ) ) ) ;
}
}
else if ( OldDeathState = = ELyraDeathState : : DeathStarted )
{
if ( NewDeathState = = ELyraDeathState : : DeathFinished )
{
FinishDeath ( ) ;
}
else
{
UE_LOG ( LogLyra , Error , TEXT ( " LyraHealthComponent: Invalid death transition [%d] -> [%d] for owner [%s]. " ) , ( uint8 ) OldDeathState , ( uint8 ) NewDeathState , * GetNameSafe ( GetOwner ( ) ) ) ;
}
}
ensureMsgf ( ( DeathState = = NewDeathState ) , TEXT ( " LyraHealthComponent: Death transition failed [%d] -> [%d] for owner [%s]. " ) , ( uint8 ) OldDeathState , ( uint8 ) NewDeathState , * GetNameSafe ( GetOwner ( ) ) ) ;
}
void ULyraHealthComponent : : StartDeath ( )
{
if ( DeathState ! = ELyraDeathState : : NotDead )
{
return ;
}
DeathState = ELyraDeathState : : DeathStarted ;
if ( AbilitySystemComponent )
{
AbilitySystemComponent - > SetLooseGameplayTagCount ( FLyraGameplayTags : : Get ( ) . Status_Death_Dying , 1 ) ;
}
AActor * Owner = GetOwner ( ) ;
check ( Owner ) ;
OnDeathStarted . Broadcast ( Owner ) ;
Owner - > ForceNetUpdate ( ) ;
}
void ULyraHealthComponent : : FinishDeath ( )
{
if ( DeathState ! = ELyraDeathState : : DeathStarted )
{
return ;
}
DeathState = ELyraDeathState : : DeathFinished ;
if ( AbilitySystemComponent )
{
AbilitySystemComponent - > SetLooseGameplayTagCount ( FLyraGameplayTags : : Get ( ) . Status_Death_Dead , 1 ) ;
}
AActor * Owner = GetOwner ( ) ;
check ( Owner ) ;
OnDeathFinished . Broadcast ( Owner ) ;
Owner - > ForceNetUpdate ( ) ;
}
void ULyraHealthComponent : : DamageSelfDestruct ( bool bFellOutOfWorld )
{
if ( ( DeathState = = ELyraDeathState : : NotDead ) & & AbilitySystemComponent )
{
const TSubclassOf < UGameplayEffect > DamageGE = ULyraAssetManager : : GetSubclass ( ULyraGameData : : Get ( ) . DamageGameplayEffect_SetByCaller ) ;
if ( ! DamageGE )
{
UE_LOG ( LogLyra , Error , TEXT ( " LyraHealthComponent: DamageSelfDestruct failed for owner [%s]. Unable to find gameplay effect [%s]. " ) , * GetNameSafe ( GetOwner ( ) ) , * ULyraGameData : : Get ( ) . DamageGameplayEffect_SetByCaller . GetAssetName ( ) ) ;
return ;
}
FGameplayEffectSpecHandle SpecHandle = AbilitySystemComponent - > MakeOutgoingSpec ( DamageGE , 1.0f , AbilitySystemComponent - > MakeEffectContext ( ) ) ;
FGameplayEffectSpec * Spec = SpecHandle . Data . Get ( ) ;
if ( ! Spec )
{
UE_LOG ( LogLyra , Error , TEXT ( " LyraHealthComponent: DamageSelfDestruct failed for owner [%s]. Unable to make outgoing spec for [%s]. " ) , * GetNameSafe ( GetOwner ( ) ) , * GetNameSafe ( DamageGE ) ) ;
return ;
}
Spec - > AddDynamicAssetTag ( TAG_Gameplay_DamageSelfDestruct ) ;
if ( bFellOutOfWorld )
{
Spec - > AddDynamicAssetTag ( TAG_Gameplay_FellOutOfWorld ) ;
}
const float DamageAmount = GetMaxHealth ( ) ;
Spec - > SetSetByCallerMagnitude ( FLyraGameplayTags : : Get ( ) . SetByCaller_Damage , DamageAmount ) ;
AbilitySystemComponent - > ApplyGameplayEffectSpecToSelf ( * Spec ) ;
}
}