RealtimeStyleTransferRuntime/Source/LyraGame/GameModes/LyraGameMode.cpp

381 lines
13 KiB
C++

// 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<ALyraPlayerState>())
{
if (const ULyraPawnData* PawnData = LyraPS->GetPawnData<ULyraPawnData>())
{
return PawnData;
}
}
}
// If not, fall back to the the default for the current experience
check(GameState);
ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass<ULyraExperienceManagerComponent>();
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<ULyraDeveloperSettings>()->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<ALyraWorldSettings>(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<ULyraExperienceManagerComponent>();
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<APlayerController>(*Iterator);
if ((PC != nullptr) && (PC->GetPawn() == nullptr))
{
if (PlayerCanRestart(PC))
{
RestartPlayer(PC);
}
}
}
}
bool ALyraGameMode::IsExperienceLoaded() const
{
check(GameState);
ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass<ULyraExperienceManagerComponent>();
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<APawn>(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<ULyraPlayerSpawningManagerComponent>())
{
return PlayerSpawningComponent->ChoosePlayerStart(Player);
}
return Super::ChoosePlayerStart_Implementation(Player);
}
void ALyraGameMode::FinishRestartPlayer(AController* NewPlayer, const FRotator& StartRotation)
{
if (ULyraPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass<ULyraPlayerSpawningManagerComponent>())
{
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<APlayerController>(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<ULyraPlayerSpawningManagerComponent>())
{
return PlayerSpawningComponent->ControllerCanRestart(Controller);
}
return true;
}
void ALyraGameMode::InitGameState()
{
Super::InitGameState();
// Listen for the experience load to complete
ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass<ULyraExperienceManagerComponent>();
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<APlayerController>(Controller))
{
GetWorldTimerManager().SetTimerForNextTick(PC, &APlayerController::ServerRestartPlayer_Implementation);
}
else if (ALyraPlayerBotController* BotController = Cast<ALyraPlayerBotController>(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<APlayerController>(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));
}
}