// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraPlayerState.h" #include "AbilitySystem/Attributes/LyraCombatSet.h" #include "AbilitySystem/Attributes/LyraHealthSet.h" #include "AbilitySystem/LyraAbilitySet.h" #include "AbilitySystem/LyraAbilitySystemComponent.h" #include "AbilitySystemComponent.h" #include "Character/LyraPawnData.h" #include "Character/LyraPawnExtensionComponent.h" #include "Components/GameFrameworkComponentManager.h" #include "Containers/Array.h" #include "Containers/UnrealString.h" #include "CoreTypes.h" #include "Delegates/Delegate.h" #include "Engine/EngineBaseTypes.h" #include "Engine/EngineTypes.h" #include "Engine/World.h" #include "GameFramework/GameStateBase.h" #include "GameFramework/GameplayMessageSubsystem.h" #include "GameFramework/Pawn.h" #include "GameModes/LyraExperienceManagerComponent.h" //@TODO: Would like to isolate this a bit better to get the pawn data in here without this having to know about other stuff #include "GameModes/LyraGameMode.h" #include "GameplayTagContainer.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "LyraLogChannels.h" #include "LyraPlayerController.h" #include "Misc/AssertionMacros.h" #include "Net/Core/PushModel/PushModel.h" #include "Net/UnrealNetwork.h" #include "Trace/Detail/Channel.h" #include "UObject/NameTypes.h" #include "UObject/UObjectBaseUtility.h" class AController; class APlayerState; class FLifetimeProperty; const FName ALyraPlayerState::NAME_LyraAbilityReady("LyraAbilitiesReady"); ALyraPlayerState::ALyraPlayerState(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , MyPlayerConnectionType(ELyraPlayerConnectionType::Player) { AbilitySystemComponent = ObjectInitializer.CreateDefaultSubobject(this, TEXT("AbilitySystemComponent")); AbilitySystemComponent->SetIsReplicated(true); AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed); CreateDefaultSubobject(TEXT("HealthSet")); CreateDefaultSubobject(TEXT("CombatSet")); // AbilitySystemComponent needs to be updated at a high frequency. NetUpdateFrequency = 100.0f; MyTeamID = FGenericTeamId::NoTeam; MySquadID = INDEX_NONE; } void ALyraPlayerState::PreInitializeComponents() { Super::PreInitializeComponents(); } void ALyraPlayerState::Reset() { Super::Reset(); } void ALyraPlayerState::ClientInitialize(AController* C) { Super::ClientInitialize(C); if (ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(GetPawn())) { PawnExtComp->CheckDefaultInitialization(); } } void ALyraPlayerState::CopyProperties(APlayerState* PlayerState) { Super::CopyProperties(PlayerState); //@TODO: Copy stats } void ALyraPlayerState::OnDeactivated() { bool bDestroyDeactivatedPlayerState = false; switch (GetPlayerConnectionType()) { case ELyraPlayerConnectionType::Player: case ELyraPlayerConnectionType::InactivePlayer: //@TODO: Ask the experience if we should destroy disconnecting players immediately or leave them around // (e.g., for long running servers where they might build up if lots of players cycle through) bDestroyDeactivatedPlayerState = true; break; default: bDestroyDeactivatedPlayerState = true; break; } SetPlayerConnectionType(ELyraPlayerConnectionType::InactivePlayer); if (bDestroyDeactivatedPlayerState) { Destroy(); } } void ALyraPlayerState::OnReactivated() { if (GetPlayerConnectionType() == ELyraPlayerConnectionType::InactivePlayer) { SetPlayerConnectionType(ELyraPlayerConnectionType::Player); } } void ALyraPlayerState::OnExperienceLoaded(const ULyraExperienceDefinition* /*CurrentExperience*/) { if (ALyraGameMode* LyraGameMode = GetWorld()->GetAuthGameMode()) { if (const ULyraPawnData* NewPawnData = LyraGameMode->GetPawnDataForController(GetOwningController())) { SetPawnData(NewPawnData); } else { UE_LOG(LogLyra, Error, TEXT("ALyraPlayerState::OnExperienceLoaded(): Unable to find PawnData to initialize player state [%s]!"), *GetNameSafe(this)); } } } void ALyraPlayerState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); FDoRepLifetimeParams SharedParams; SharedParams.bIsPushBased = true; DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, PawnData, SharedParams); DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, MyPlayerConnectionType, SharedParams) DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, MyTeamID, SharedParams); DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, MySquadID, SharedParams); DOREPLIFETIME(ThisClass, StatTags); } ALyraPlayerController* ALyraPlayerState::GetLyraPlayerController() const { return Cast(GetOwner()); } UAbilitySystemComponent* ALyraPlayerState::GetAbilitySystemComponent() const { return GetLyraAbilitySystemComponent(); } void ALyraPlayerState::PostInitializeComponents() { Super::PostInitializeComponents(); check(AbilitySystemComponent); AbilitySystemComponent->InitAbilityActorInfo(this, GetPawn()); if (GetNetMode() != NM_Client) { AGameStateBase* GameState = GetWorld()->GetGameState(); check(GameState); ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass(); check(ExperienceComponent); ExperienceComponent->CallOrRegister_OnExperienceLoaded(FOnLyraExperienceLoaded::FDelegate::CreateUObject(this, &ThisClass::OnExperienceLoaded)); } } void ALyraPlayerState::SetPawnData(const ULyraPawnData* InPawnData) { check(InPawnData); if (GetLocalRole() != ROLE_Authority) { return; } if (PawnData) { UE_LOG(LogLyra, Error, TEXT("Trying to set PawnData [%s] on player state [%s] that already has valid PawnData [%s]."), *GetNameSafe(InPawnData), *GetNameSafe(this), *GetNameSafe(PawnData)); return; } MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, PawnData, this); PawnData = InPawnData; for (const ULyraAbilitySet* AbilitySet : PawnData->AbilitySets) { if (AbilitySet) { AbilitySet->GiveToAbilitySystem(AbilitySystemComponent, nullptr); } } UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, NAME_LyraAbilityReady); ForceNetUpdate(); } void ALyraPlayerState::OnRep_PawnData() { } void ALyraPlayerState::SetPlayerConnectionType(ELyraPlayerConnectionType NewType) { MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, MyPlayerConnectionType, this); MyPlayerConnectionType = NewType; } void ALyraPlayerState::SetSquadID(int32 NewSquadId) { if (HasAuthority()) { MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, MySquadID, this); MySquadID = NewSquadId; } } void ALyraPlayerState::SetGenericTeamId(const FGenericTeamId& NewTeamID) { if (HasAuthority()) { const FGenericTeamId OldTeamID = MyTeamID; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, MyTeamID, this); MyTeamID = NewTeamID; ConditionalBroadcastTeamChanged(this, OldTeamID, NewTeamID); } else { UE_LOG(LogLyraTeams, Error, TEXT("Cannot set team for %s on non-authority"), *GetPathName(this)); } } FGenericTeamId ALyraPlayerState::GetGenericTeamId() const { return MyTeamID; } FOnLyraTeamIndexChangedDelegate* ALyraPlayerState::GetOnTeamIndexChangedDelegate() { return &OnTeamChangedDelegate; } void ALyraPlayerState::OnRep_MyTeamID(FGenericTeamId OldTeamID) { ConditionalBroadcastTeamChanged(this, OldTeamID, MyTeamID); } void ALyraPlayerState::OnRep_MySquadID() { //@TODO: Let the squad subsystem know (once that exists) } void ALyraPlayerState::AddStatTagStack(FGameplayTag Tag, int32 StackCount) { StatTags.AddStack(Tag, StackCount); } void ALyraPlayerState::RemoveStatTagStack(FGameplayTag Tag, int32 StackCount) { StatTags.RemoveStack(Tag, StackCount); } int32 ALyraPlayerState::GetStatTagStackCount(FGameplayTag Tag) const { return StatTags.GetStackCount(Tag); } bool ALyraPlayerState::HasStatTag(FGameplayTag Tag) const { return StatTags.ContainsTag(Tag); } void ALyraPlayerState::ClientBroadcastMessage_Implementation(const FLyraVerbMessage Message) { // This check is needed to prevent running the action when in standalone mode if (GetNetMode() == NM_Client) { UGameplayMessageSubsystem::Get(this).BroadcastMessage(Message.Verb, Message); } }