329 lines
10 KiB
C++
329 lines
10 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "LyraPawnExtensionComponent.h"
|
|
|
|
#include "Abilities/GameplayAbilityTypes.h"
|
|
#include "AbilitySystem/LyraAbilitySystemComponent.h"
|
|
#include "Components/GameFrameworkComponentManager.h"
|
|
#include "Containers/Array.h"
|
|
#include "Containers/Set.h"
|
|
#include "Containers/UnrealString.h"
|
|
#include "Engine/EngineBaseTypes.h"
|
|
#include "GameFramework/Controller.h"
|
|
#include "GameFramework/Pawn.h"
|
|
#include "GameplayTagContainer.h"
|
|
#include "HAL/Platform.h"
|
|
#include "Logging/LogCategory.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "LyraGameplayTags.h"
|
|
#include "LyraLogChannels.h"
|
|
#include "LyraPawnData.h"
|
|
#include "Misc/AssertionMacros.h"
|
|
#include "Net/UnrealNetwork.h"
|
|
#include "Templates/SharedPointer.h"
|
|
#include "Trace/Detail/Channel.h"
|
|
#include "UObject/UObjectBaseUtility.h"
|
|
#include "UObject/UnrealNames.h"
|
|
#include "UObject/WeakObjectPtr.h"
|
|
#include "UObject/WeakObjectPtrTemplates.h"
|
|
|
|
class FLifetimeProperty;
|
|
class UActorComponent;
|
|
|
|
const FName ULyraPawnExtensionComponent::NAME_ActorFeatureName("PawnExtension");
|
|
|
|
ULyraPawnExtensionComponent::ULyraPawnExtensionComponent(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
PrimaryComponentTick.bStartWithTickEnabled = false;
|
|
PrimaryComponentTick.bCanEverTick = false;
|
|
|
|
SetIsReplicatedByDefault(true);
|
|
|
|
PawnData = nullptr;
|
|
AbilitySystemComponent = nullptr;
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
|
{
|
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
|
|
DOREPLIFETIME(ULyraPawnExtensionComponent, PawnData);
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::OnRegister()
|
|
{
|
|
Super::OnRegister();
|
|
|
|
const APawn* Pawn = GetPawn<APawn>();
|
|
ensureAlwaysMsgf((Pawn != nullptr), TEXT("LyraPawnExtensionComponent on [%s] can only be added to Pawn actors."), *GetNameSafe(GetOwner()));
|
|
|
|
TArray<UActorComponent*> PawnExtensionComponents;
|
|
Pawn->GetComponents(ULyraPawnExtensionComponent::StaticClass(), PawnExtensionComponents);
|
|
ensureAlwaysMsgf((PawnExtensionComponents.Num() == 1), TEXT("Only one LyraPawnExtensionComponent should exist on [%s]."), *GetNameSafe(GetOwner()));
|
|
|
|
// Register with the init state system early, this will only work if this is a game world
|
|
RegisterInitStateFeature();
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
// Listen for changes to all features
|
|
BindOnActorInitStateChanged(NAME_None, FGameplayTag(), false);
|
|
|
|
// Notifies state manager that we have spawned, then try rest of default initialization
|
|
ensure(TryToChangeInitState(FLyraGameplayTags::Get().InitState_Spawned));
|
|
CheckDefaultInitialization();
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|
{
|
|
UninitializeAbilitySystem();
|
|
UnregisterInitStateFeature();
|
|
|
|
Super::EndPlay(EndPlayReason);
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::SetPawnData(const ULyraPawnData* InPawnData)
|
|
{
|
|
check(InPawnData);
|
|
|
|
APawn* Pawn = GetPawnChecked<APawn>();
|
|
|
|
if (Pawn->GetLocalRole() != ROLE_Authority)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (PawnData)
|
|
{
|
|
UE_LOG(LogLyra, Error, TEXT("Trying to set PawnData [%s] on pawn [%s] that already has valid PawnData [%s]."), *GetNameSafe(InPawnData), *GetNameSafe(Pawn), *GetNameSafe(PawnData));
|
|
return;
|
|
}
|
|
|
|
PawnData = InPawnData;
|
|
|
|
Pawn->ForceNetUpdate();
|
|
|
|
CheckDefaultInitialization();
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::OnRep_PawnData()
|
|
{
|
|
CheckDefaultInitialization();
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::InitializeAbilitySystem(ULyraAbilitySystemComponent* InASC, AActor* InOwnerActor)
|
|
{
|
|
check(InASC);
|
|
check(InOwnerActor);
|
|
|
|
if (AbilitySystemComponent == InASC)
|
|
{
|
|
// The ability system component hasn't changed.
|
|
return;
|
|
}
|
|
|
|
if (AbilitySystemComponent)
|
|
{
|
|
// Clean up the old ability system component.
|
|
UninitializeAbilitySystem();
|
|
}
|
|
|
|
APawn* Pawn = GetPawnChecked<APawn>();
|
|
AActor* ExistingAvatar = InASC->GetAvatarActor();
|
|
|
|
UE_LOG(LogLyra, Verbose, TEXT("Setting up ASC [%s] on pawn [%s] owner [%s], existing [%s] "), *GetNameSafe(InASC), *GetNameSafe(Pawn), *GetNameSafe(InOwnerActor), *GetNameSafe(ExistingAvatar));
|
|
|
|
if ((ExistingAvatar != nullptr) && (ExistingAvatar != Pawn))
|
|
{
|
|
UE_LOG(LogLyra, Log, TEXT("Existing avatar (authority=%d)"), ExistingAvatar->HasAuthority() ? 1 : 0);
|
|
|
|
// There is already a pawn acting as the ASC's avatar, so we need to kick it out
|
|
// This can happen on clients if they're lagged: their new pawn is spawned + possessed before the dead one is removed
|
|
ensure(!ExistingAvatar->HasAuthority());
|
|
|
|
if (ULyraPawnExtensionComponent* OtherExtensionComponent = FindPawnExtensionComponent(ExistingAvatar))
|
|
{
|
|
OtherExtensionComponent->UninitializeAbilitySystem();
|
|
}
|
|
}
|
|
|
|
AbilitySystemComponent = InASC;
|
|
AbilitySystemComponent->InitAbilityActorInfo(InOwnerActor, Pawn);
|
|
|
|
if (ensure(PawnData))
|
|
{
|
|
InASC->SetTagRelationshipMapping(PawnData->TagRelationshipMapping);
|
|
}
|
|
|
|
OnAbilitySystemInitialized.Broadcast();
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::UninitializeAbilitySystem()
|
|
{
|
|
if (!AbilitySystemComponent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Uninitialize the ASC if we're still the avatar actor (otherwise another pawn already did it when they became the avatar actor)
|
|
if (AbilitySystemComponent->GetAvatarActor() == GetOwner())
|
|
{
|
|
FGameplayTagContainer AbilityTypesToIgnore;
|
|
AbilityTypesToIgnore.AddTag(FLyraGameplayTags::Get().Ability_Behavior_SurvivesDeath);
|
|
|
|
AbilitySystemComponent->CancelAbilities(nullptr, &AbilityTypesToIgnore);
|
|
AbilitySystemComponent->ClearAbilityInput();
|
|
AbilitySystemComponent->RemoveAllGameplayCues();
|
|
|
|
if (AbilitySystemComponent->GetOwnerActor() != nullptr)
|
|
{
|
|
AbilitySystemComponent->SetAvatarActor(nullptr);
|
|
}
|
|
else
|
|
{
|
|
// If the ASC doesn't have a valid owner, we need to clear *all* actor info, not just the avatar pairing
|
|
AbilitySystemComponent->ClearActorInfo();
|
|
}
|
|
|
|
OnAbilitySystemUninitialized.Broadcast();
|
|
}
|
|
|
|
AbilitySystemComponent = nullptr;
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::HandleControllerChanged()
|
|
{
|
|
if (AbilitySystemComponent && (AbilitySystemComponent->GetAvatarActor() == GetPawnChecked<APawn>()))
|
|
{
|
|
ensure(AbilitySystemComponent->AbilityActorInfo->OwnerActor == AbilitySystemComponent->GetOwnerActor());
|
|
if (AbilitySystemComponent->GetOwnerActor() == nullptr)
|
|
{
|
|
UninitializeAbilitySystem();
|
|
}
|
|
else
|
|
{
|
|
AbilitySystemComponent->RefreshAbilityActorInfo();
|
|
}
|
|
}
|
|
|
|
CheckDefaultInitialization();
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::HandlePlayerStateReplicated()
|
|
{
|
|
CheckDefaultInitialization();
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::SetupPlayerInputComponent()
|
|
{
|
|
CheckDefaultInitialization();
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::CheckDefaultInitialization()
|
|
{
|
|
// Before checking our progress, try progressing any other features we might depend on
|
|
CheckDefaultInitializationForImplementers();
|
|
|
|
const FLyraGameplayTags& InitTags = FLyraGameplayTags::Get();
|
|
static const TArray<FGameplayTag> StateChain = { InitTags.InitState_Spawned, InitTags.InitState_DataAvailable, InitTags.InitState_DataInitialized, InitTags.InitState_GameplayReady };
|
|
|
|
// This will try to progress from spawned (which is only set in BeginPlay) through the data initialization stages until it gets to gameplay ready
|
|
ContinueInitStateChain(StateChain);
|
|
}
|
|
|
|
bool ULyraPawnExtensionComponent::CanChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) const
|
|
{
|
|
check(Manager);
|
|
|
|
APawn* Pawn = GetPawn<APawn>();
|
|
const FLyraGameplayTags& InitTags = FLyraGameplayTags::Get();
|
|
|
|
if (!CurrentState.IsValid() && DesiredState == InitTags.InitState_Spawned)
|
|
{
|
|
// As long as we are on a valid pawn, we count as spawned
|
|
if (Pawn)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
if (CurrentState == InitTags.InitState_Spawned && DesiredState == InitTags.InitState_DataAvailable)
|
|
{
|
|
// Pawn data is required.
|
|
if (!PawnData)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const bool bHasAuthority = Pawn->HasAuthority();
|
|
const bool bIsLocallyControlled = Pawn->IsLocallyControlled();
|
|
|
|
if (bHasAuthority || bIsLocallyControlled)
|
|
{
|
|
// Check for being possessed by a controller.
|
|
if (!GetController<AController>())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if (CurrentState == InitTags.InitState_DataAvailable && DesiredState == InitTags.InitState_DataInitialized)
|
|
{
|
|
// Transition to initialize if all features have their data available
|
|
return Manager->HaveAllFeaturesReachedInitState(Pawn, InitTags.InitState_DataAvailable);
|
|
}
|
|
else if (CurrentState == InitTags.InitState_DataInitialized && DesiredState == InitTags.InitState_GameplayReady)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState)
|
|
{
|
|
if (DesiredState == FLyraGameplayTags::Get().InitState_DataInitialized)
|
|
{
|
|
// This is currently all handled by other components listening to this state change
|
|
}
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::OnActorInitStateChanged(const FActorInitStateChangedParams& Params)
|
|
{
|
|
// If another feature is now in DataAvailable, see if we should transition to DataInitialized
|
|
if (Params.FeatureName != NAME_ActorFeatureName)
|
|
{
|
|
const FLyraGameplayTags& InitTags = FLyraGameplayTags::Get();
|
|
if (Params.FeatureState == InitTags.InitState_DataAvailable)
|
|
{
|
|
CheckDefaultInitialization();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::OnAbilitySystemInitialized_RegisterAndCall(FSimpleMulticastDelegate::FDelegate Delegate)
|
|
{
|
|
if (!OnAbilitySystemInitialized.IsBoundToObject(Delegate.GetUObject()))
|
|
{
|
|
OnAbilitySystemInitialized.Add(Delegate);
|
|
}
|
|
|
|
if (AbilitySystemComponent)
|
|
{
|
|
Delegate.Execute();
|
|
}
|
|
}
|
|
|
|
void ULyraPawnExtensionComponent::OnAbilitySystemUninitialized_Register(FSimpleMulticastDelegate::FDelegate Delegate)
|
|
{
|
|
if (!OnAbilitySystemUninitialized.IsBoundToObject(Delegate.GetUObject()))
|
|
{
|
|
OnAbilitySystemUninitialized.Add(Delegate);
|
|
}
|
|
}
|