// 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& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(ULyraPawnExtensionComponent, PawnData); } void ULyraPawnExtensionComponent::OnRegister() { Super::OnRegister(); const APawn* Pawn = GetPawn(); ensureAlwaysMsgf((Pawn != nullptr), TEXT("LyraPawnExtensionComponent on [%s] can only be added to Pawn actors."), *GetNameSafe(GetOwner())); TArray 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(); 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(); 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())) { 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 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(); 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()) { 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); } }