// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraPlayerSpawningManagerComponent.h" #include "Engine/World.h" #include "GameFramework/Controller.h" #include "GameFramework/GameState.h" #include "GameFramework/PlayerController.h" #include "GameFramework/PlayerState.h" #include "GameFramework/PlayerStart.h" #include "EngineUtils.h" #include "Engine/PlayerStartPIE.h" #include "LyraPlayerStart.h" DEFINE_LOG_CATEGORY_STATIC(LogPlayerSpawning, Log, All); ULyraPlayerSpawningManagerComponent::ULyraPlayerSpawningManagerComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SetIsReplicatedByDefault(false); bAutoRegister = true; bAutoActivate = true; bWantsInitializeComponent = true; PrimaryComponentTick.TickGroup = TG_PrePhysics; PrimaryComponentTick.bCanEverTick = true; PrimaryComponentTick.bAllowTickOnDedicatedServer = true; PrimaryComponentTick.bStartWithTickEnabled = false; } void ULyraPlayerSpawningManagerComponent::InitializeComponent() { Super::InitializeComponent(); FWorldDelegates::LevelAddedToWorld.AddUObject(this, &ThisClass::OnLevelAdded); UWorld* World = GetWorld(); World->AddOnActorSpawnedHandler(FOnActorSpawned::FDelegate::CreateUObject(this, &ThisClass::HandleOnActorSpawned)); for (TActorIterator It(World); It; ++It) { if (ALyraPlayerStart* PlayerStart = *It) { CachedPlayerStarts.Add(PlayerStart); } } } void ULyraPlayerSpawningManagerComponent::OnLevelAdded(ULevel* InLevel, UWorld* InWorld) { if (InWorld == GetWorld()) { for (AActor* Actor : InLevel->Actors) { if (ALyraPlayerStart* PlayerStart = Cast(Actor)) { ensure(!CachedPlayerStarts.Contains(PlayerStart)); CachedPlayerStarts.Add(PlayerStart); } } } } void ULyraPlayerSpawningManagerComponent::HandleOnActorSpawned(AActor* SpawnedActor) { if (ALyraPlayerStart* PlayerStart = Cast(SpawnedActor)) { CachedPlayerStarts.Add(PlayerStart); } } // ALyraGameMode Proxied Calls - Need to handle when someone chooses // to restart a player the normal way in the engine. //====================================================================== AActor* ULyraPlayerSpawningManagerComponent::ChoosePlayerStart(AController* Player) { if (Player) { #if WITH_EDITOR if (APlayerStart* PlayerStart = FindPlayFromHereStart(Player)) { return PlayerStart; } #endif TArray StarterPoints; for (auto StartIt = CachedPlayerStarts.CreateIterator(); StartIt; ++StartIt) { if (ALyraPlayerStart* Start = (*StartIt).Get()) { StarterPoints.Add(Start); } else { StartIt.RemoveCurrent(); } } if (APlayerState* PlayerState = Player->GetPlayerState()) { // start dedicated spectators at any random starting location, but they do not claim it if (PlayerState->IsOnlyASpectator()) { if (!StarterPoints.IsEmpty()) { return StarterPoints[FMath::RandRange(0, StarterPoints.Num() - 1)]; } return nullptr; } } AActor* PlayerStart = OnChoosePlayerStart(Player, StarterPoints); if (!PlayerStart) { PlayerStart = GetFirstRandomUnoccupiedPlayerStart(Player, StarterPoints); } if (ALyraPlayerStart* LyraStart = Cast(PlayerStart)) { LyraStart->TryClaim(Player); } return PlayerStart; } return nullptr; } #if WITH_EDITOR APlayerStart* ULyraPlayerSpawningManagerComponent::FindPlayFromHereStart(AController* Player) { // Only 'Play From Here' for a player controller, bots etc. should all spawn from normal spawn points. if (Player->IsA()) { if (UWorld* World = GetWorld()) { for (TActorIterator It(World); It; ++It) { if (APlayerStart* PlayerStart = *It) { if (PlayerStart->IsA()) { // Always prefer the first "Play from Here" PlayerStart, if we find one while in PIE mode return PlayerStart; } } } } } return nullptr; } #endif bool ULyraPlayerSpawningManagerComponent::ControllerCanRestart(AController* Player) { bool bCanRestart = true; // TODO Can they restart? return bCanRestart; } void ULyraPlayerSpawningManagerComponent::FinishRestartPlayer(AController* NewPlayer, const FRotator& StartRotation) { OnFinishRestartPlayer(NewPlayer, StartRotation); K2_OnFinishRestartPlayer(NewPlayer, StartRotation); } //================================================================ void ULyraPlayerSpawningManagerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); } APlayerStart* ULyraPlayerSpawningManagerComponent::GetFirstRandomUnoccupiedPlayerStart(AController* Controller, const TArray& StartPoints) const { if (Controller) { TArray UnOccupiedStartPoints; TArray OccupiedStartPoints; for (ALyraPlayerStart* StartPoint : StartPoints) { ELyraPlayerStartLocationOccupancy State = StartPoint->GetLocationOccupancy(Controller); switch (State) { case ELyraPlayerStartLocationOccupancy::Empty: UnOccupiedStartPoints.Add(StartPoint); break; case ELyraPlayerStartLocationOccupancy::Partial: OccupiedStartPoints.Add(StartPoint); break; } } if (UnOccupiedStartPoints.Num() > 0) { return UnOccupiedStartPoints[FMath::RandRange(0, UnOccupiedStartPoints.Num() - 1)]; } else if (OccupiedStartPoints.Num() > 0) { return OccupiedStartPoints[FMath::RandRange(0, OccupiedStartPoints.Num() - 1)]; } } return nullptr; }