// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraGameMode.h" #include "LyraLogChannels.h" #include "System/LyraAssetManager.h" #include "LyraGameState.h" #include "System/LyraGameSession.h" #include "Player/LyraPlayerController.h" #include "Player/LyraPlayerBotController.h" #include "Player/LyraPlayerState.h" #include "Character/LyraCharacter.h" #include "UI/LyraHUD.h" #include "Character/LyraPawnExtensionComponent.h" #include "Character/LyraPawnData.h" #include "GameModes/LyraWorldSettings.h" #include "GameModes/LyraExperienceDefinition.h" #include "GameModes/LyraExperienceManagerComponent.h" #include "Kismet/GameplayStatics.h" #include "Development/LyraDeveloperSettings.h" #include "Player/LyraPlayerSpawningManagerComponent.h" #include "TimerManager.h" ALyraGameMode::ALyraGameMode(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { GameStateClass = ALyraGameState::StaticClass(); GameSessionClass = ALyraGameSession::StaticClass(); PlayerControllerClass = ALyraPlayerController::StaticClass(); ReplaySpectatorPlayerControllerClass = ALyraReplayPlayerController::StaticClass(); PlayerStateClass = ALyraPlayerState::StaticClass(); DefaultPawnClass = ALyraCharacter::StaticClass(); HUDClass = ALyraHUD::StaticClass(); } const ULyraPawnData* ALyraGameMode::GetPawnDataForController(const AController* InController) const { // See if pawn data is already set on the player state if (InController != nullptr) { if (const ALyraPlayerState* LyraPS = InController->GetPlayerState()) { if (const ULyraPawnData* PawnData = LyraPS->GetPawnData()) { return PawnData; } } } // If not, fall back to the the default for the current experience check(GameState); ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass(); check(ExperienceComponent); if (ExperienceComponent->IsExperienceLoaded()) { const ULyraExperienceDefinition* Experience = ExperienceComponent->GetCurrentExperienceChecked(); if (Experience->DefaultPawnData != nullptr) { return Experience->DefaultPawnData; } // Experience is loaded and there's still no pawn data, fall back to the default for now return ULyraAssetManager::Get().GetDefaultPawnData(); } // Experience not loaded yet, so there is no pawn data to be had return nullptr; } void ALyraGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) { Super::InitGame(MapName, Options, ErrorMessage); //@TODO: Eventually only do this for PIE/auto GetWorld()->GetTimerManager().SetTimerForNextTick(this, &ThisClass::HandleMatchAssignmentIfNotExpectingOne); } void ALyraGameMode::HandleMatchAssignmentIfNotExpectingOne() { FPrimaryAssetId ExperienceId; FString ExperienceIdSource; // Precedence order (highest wins) // - Matchmaking assignment (if present) // - URL Options override // - Developer Settings (PIE only) // - Command Line override // - World Settings // - Default experience UWorld* World = GetWorld(); if (!ExperienceId.IsValid() && UGameplayStatics::HasOption(OptionsString, TEXT("Experience"))) { const FString ExperienceFromOptions = UGameplayStatics::ParseOption(OptionsString, TEXT("Experience")); ExperienceId = FPrimaryAssetId(FPrimaryAssetType(ULyraExperienceDefinition::StaticClass()->GetFName()), FName(*ExperienceFromOptions)); ExperienceIdSource = TEXT("OptionsString"); } if (!ExperienceId.IsValid() && World->IsPlayInEditor()) { ExperienceId = GetDefault()->ExperienceOverride; ExperienceIdSource = TEXT("DeveloperSettings"); } // see if the command line wants to set the experience if (!ExperienceId.IsValid()) { FString ExperienceFromCommandLine; if (FParse::Value(FCommandLine::Get(), TEXT("Experience="), ExperienceFromCommandLine)) { ExperienceId = FPrimaryAssetId::ParseTypeAndName(ExperienceFromCommandLine); ExperienceIdSource = TEXT("CommandLine"); } } // see if the world settings has a default experience if (!ExperienceId.IsValid()) { if (ALyraWorldSettings* TypedWorldSettings = Cast(GetWorldSettings())) { ExperienceId = TypedWorldSettings->GetDefaultGameplayExperience(); ExperienceIdSource = TEXT("WorldSettings"); } } ULyraAssetManager& AssetManager = ULyraAssetManager::Get(); FAssetData Dummy; if (ExperienceId.IsValid() && !AssetManager.GetPrimaryAssetData(ExperienceId, /*out*/ Dummy)) { UE_LOG(LogLyraExperience, Error, TEXT("EXPERIENCE: Wanted to use %s but couldn't find it, falling back to the default)"), *ExperienceId.ToString()); ExperienceId = FPrimaryAssetId(); } // Final fallback to the default experience if (!ExperienceId.IsValid()) { //@TODO: Pull this from a config setting or something ExperienceId = FPrimaryAssetId(FPrimaryAssetType("LyraExperienceDefinition"), FName("B_LyraDefaultExperience")); ExperienceIdSource = TEXT("Default"); } OnMatchAssignmentGiven(ExperienceId, ExperienceIdSource); } void ALyraGameMode::OnMatchAssignmentGiven(FPrimaryAssetId ExperienceId, const FString& ExperienceIdSource) { #if WITH_SERVER_CODE if (ExperienceId.IsValid()) { UE_LOG(LogLyraExperience, Log, TEXT("Identified experience %s (Source: %s)"), *ExperienceId.ToString(), *ExperienceIdSource); ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass(); check(ExperienceComponent); ExperienceComponent->ServerSetCurrentExperience(ExperienceId); } else { UE_LOG(LogLyraExperience, Error, TEXT("Failed to identify experience, loading screen will stay up forever")); } #endif } void ALyraGameMode::OnExperienceLoaded(const ULyraExperienceDefinition* CurrentExperience) { // Spawn any players that are already attached //@TODO: Here we're handling only *player* controllers, but in GetDefaultPawnClassForController_Implementation we skipped all controllers // GetDefaultPawnClassForController_Implementation might only be getting called for players anyways for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator) { APlayerController* PC = Cast(*Iterator); if ((PC != nullptr) && (PC->GetPawn() == nullptr)) { if (PlayerCanRestart(PC)) { RestartPlayer(PC); } } } } bool ALyraGameMode::IsExperienceLoaded() const { check(GameState); ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass(); check(ExperienceComponent); return ExperienceComponent->IsExperienceLoaded(); } UClass* ALyraGameMode::GetDefaultPawnClassForController_Implementation(AController* InController) { if (const ULyraPawnData* PawnData = GetPawnDataForController(InController)) { if (PawnData->PawnClass) { return PawnData->PawnClass; } } return Super::GetDefaultPawnClassForController_Implementation(InController); } APawn* ALyraGameMode::SpawnDefaultPawnAtTransform_Implementation(AController* NewPlayer, const FTransform& SpawnTransform) { FActorSpawnParameters SpawnInfo; SpawnInfo.Instigator = GetInstigator(); SpawnInfo.ObjectFlags |= RF_Transient; // Never save the default player pawns into a map. SpawnInfo.bDeferConstruction = true; if (UClass* PawnClass = GetDefaultPawnClassForController(NewPlayer)) { if (APawn* SpawnedPawn = GetWorld()->SpawnActor(PawnClass, SpawnTransform, SpawnInfo)) { if (ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(SpawnedPawn)) { if (const ULyraPawnData* PawnData = GetPawnDataForController(NewPlayer)) { PawnExtComp->SetPawnData(PawnData); } else { UE_LOG(LogLyra, Error, TEXT("Game mode was unable to set PawnData on the spawned pawn [%s]."), *GetNameSafe(SpawnedPawn)); } } SpawnedPawn->FinishSpawning(SpawnTransform); return SpawnedPawn; } else { UE_LOG(LogLyra, Error, TEXT("Game mode was unable to spawn Pawn of class [%s] at [%s]."), *GetNameSafe(PawnClass), *SpawnTransform.ToHumanReadableString()); } } else { UE_LOG(LogLyra, Error, TEXT("Game mode was unable to spawn Pawn due to NULL pawn class.")); } return nullptr; } bool ALyraGameMode::ShouldSpawnAtStartSpot(AController* Player) { // We never want to use the start spot, always use the spawn management component. return false; } void ALyraGameMode::HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer) { // Delay starting new players until the experience has been loaded // (players who log in prior to that will be started by OnExperienceLoaded) if (IsExperienceLoaded()) { Super::HandleStartingNewPlayer_Implementation(NewPlayer); } } AActor* ALyraGameMode::ChoosePlayerStart_Implementation(AController* Player) { if (ULyraPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass()) { return PlayerSpawningComponent->ChoosePlayerStart(Player); } return Super::ChoosePlayerStart_Implementation(Player); } void ALyraGameMode::FinishRestartPlayer(AController* NewPlayer, const FRotator& StartRotation) { if (ULyraPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass()) { PlayerSpawningComponent->FinishRestartPlayer(NewPlayer, StartRotation); } Super::FinishRestartPlayer(NewPlayer, StartRotation); } bool ALyraGameMode::PlayerCanRestart_Implementation(APlayerController* Player) { return ControllerCanRestart(Player); } bool ALyraGameMode::ControllerCanRestart(AController* Controller) { if (APlayerController* PC = Cast(Controller)) { if (!Super::PlayerCanRestart_Implementation(PC)) { return false; } } else { // Bot version of Super::PlayerCanRestart_Implementation if ((Controller == nullptr) || Controller->IsPendingKillPending()) { return false; } } if (ULyraPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass()) { return PlayerSpawningComponent->ControllerCanRestart(Controller); } return true; } void ALyraGameMode::InitGameState() { Super::InitGameState(); // Listen for the experience load to complete ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass(); check(ExperienceComponent); ExperienceComponent->CallOrRegister_OnExperienceLoaded(FOnLyraExperienceLoaded::FDelegate::CreateUObject(this, &ThisClass::OnExperienceLoaded)); } void ALyraGameMode::OnPostLogin(AController* NewPlayer) { Super::OnPostLogin(NewPlayer); OnGameModeCombinedPostLoginDelegate.Broadcast(this, NewPlayer); } void ALyraGameMode::RequestPlayerRestartNextFrame(AController* Controller, bool bForceReset) { if (bForceReset && (Controller != nullptr)) { Controller->Reset(); } if (APlayerController* PC = Cast(Controller)) { GetWorldTimerManager().SetTimerForNextTick(PC, &APlayerController::ServerRestartPlayer_Implementation); } else if (ALyraPlayerBotController* BotController = Cast(Controller)) { GetWorldTimerManager().SetTimerForNextTick(BotController, &ALyraPlayerBotController::ServerRestartController); } } bool ALyraGameMode::UpdatePlayerStartSpot(AController* Player, const FString& Portal, FString& OutErrorMessage) { // Do nothing, we'll wait until PostLogin when we try to spawn the player for real. // Doing anything right now is no good, systems like team assignment haven't even occurred yet. return true; } void ALyraGameMode::FailedToRestartPlayer(AController* NewPlayer) { Super::FailedToRestartPlayer(NewPlayer); // If we tried to spawn a pawn and it failed, lets try again *note* check if there's actually a pawn class // before we try this forever. if (UClass* PawnClass = GetDefaultPawnClassForController(NewPlayer)) { if (APlayerController* NewPC = Cast(NewPlayer)) { // If it's a player don't loop forever, maybe something changed and they can no longer restart if so stop trying. if (PlayerCanRestart(NewPC)) { RequestPlayerRestartNextFrame(NewPlayer, false); } else { UE_LOG(LogLyra, Verbose, TEXT("FailedToRestartPlayer(%s) and PlayerCanRestart returned false, so we're not going to try again."), *GetPathNameSafe(NewPlayer)); } } else { RequestPlayerRestartNextFrame(NewPlayer, false); } } else { UE_LOG(LogLyra, Verbose, TEXT("FailedToRestartPlayer(%s) but there's no pawn class so giving up."), *GetPathNameSafe(NewPlayer)); } }