381 lines
13 KiB
C++
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));
|
|
}
|
|
} |