OLS/Source/ols/Private/GameModes/OLSGameMode.cpp

534 lines
18 KiB
C++

// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
#include "GameModes/OLSGameMode.h"
#include "CommonUserSubsystem.h"
#include "GameMapsSettings.h"
#include "Characters/OLSCharacter.h"
#include "DataAssets/OLSExperienceDefinitionDataAsset.h"
#include "DataAssets/OLSPawnDataAsset.h"
#include "GameModes/OLSExperienceManagerComponent.h"
#include "GameModes/OLSGameState.h"
#include "Kismet/GameplayStatics.h"
#include "Player/OLSPlayerController.h"
#include "Player/OLSPlayerState.h"
#include "Systems/OLSAssetManager.h"
#include "Systems/OLSGameSession.h"
#include "UI/OLSHUD.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSGameMode)
AOLSGameMode::AOLSGameMode(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
{
GameStateClass = AOLSGameState::StaticClass();
GameSessionClass = AOLSGameSession::StaticClass();
PlayerControllerClass = AOLSPlayerController::StaticClass();
// ReplaySpectatorPlayerControllerClass = ALyraReplayPlayerController::StaticClass();
PlayerStateClass = AOLSPlayerState::StaticClass();
DefaultPawnClass = AOLSCharacter::StaticClass();
HUDClass = AOLSHUD::StaticClass();
}
const UOLSPawnDataAsset* AOLSGameMode::GetPawnDataForController(const AController* controller) const
{
// See if pawn data is already set on the player state
if (controller)
{
if (const AOLSPlayerState* playerState = controller->GetPlayerState<AOLSPlayerState>())
{
if (const UOLSPawnDataAsset* pawnData = playerState->GetPawnData<UOLSPawnDataAsset>())
{
return pawnData;
}
}
}
// If not, fall back to the the default for the current experience
check(GameState);
UOLSExperienceManagerComponent* experienceComponent = GameState->FindComponentByClass<UOLSExperienceManagerComponent>();
check(experienceComponent);
if (experienceComponent->IsExperienceLoaded())
{
const UOLSExperienceDefinitionDataAsset* experienceDataAsset = experienceComponent->GetCurrentExperienceChecked();
if (experienceDataAsset->DefaultPawnData)
{
return experienceDataAsset->DefaultPawnData;
}
// Experience is loaded and there's still no pawn data, fall back to the default for now
return UOLSAssetManager::Get().GetDefaultPawnData();
}
// Experience not loaded yet, so there is no pawn data to be had
return nullptr;
}
void AOLSGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage)
{
Super::InitGame(MapName, Options, ErrorMessage);
// Wait for the next frame to give time to initialize startup settings
GetWorld()->GetTimerManager().SetTimerForNextTick(this, &ThisClass::HandleMatchAssignmentIfNotExpectingOne);
}
UClass* AOLSGameMode::GetDefaultPawnClassForController_Implementation(AController* controller)
{
if (const UOLSPawnDataAsset* pawnData = GetPawnDataForController(controller))
{
if (pawnData->PawnClass)
{
return pawnData->PawnClass;
}
}
return Super::GetDefaultPawnClassForController_Implementation(controller);
}
APawn* AOLSGameMode::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))
{
// @TODO: Implement this as well as implement UOLSawnExtensionComponent
// if (UOLSawnExtensionComponent* PawnExtComp = UOLSawnExtensionComponent::FindPawnExtensionComponent(spawnedPawn))
// {
// if (const ULyraPawnData* PawnData = GetPawnDataForController(newPlayer))
// {
// PawnExtComp->SetPawnData(PawnData);
// }
// else
// { // @TODO: Replace this with out custom log.
// 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
{
// @TODO: Replace this with out custom log.
// UE_LOG(LogLyra, Error, TEXT("Game mode was unable to spawn Pawn of class [%s] at [%s]."), *GetNameSafe(pawnClass), *spawnTransform.ToHumanReadableString());
}
}
else
{
// @TODO: Replace this with out custom log.
// UE_LOG(LogLyra, Error, TEXT("Game mode was unable to spawn Pawn due to NULL pawn class."));
}
return nullptr;
}
bool AOLSGameMode::ShouldSpawnAtStartSpot(AController* Player)
{
// We never want to use the start spot, always use the spawn management component.
return false;
}
void AOLSGameMode::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* AOLSGameMode::ChoosePlayerStart_Implementation(AController* player)
{
// @TODO: Implement this as well as implement UOLSPlayerSpawningManagerComponent
// if (UOLSPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass<ULyraPlayerSpawningManagerComponent>())
// {
// return PlayerSpawningComponent->ChoosePlayerStart(player);
// }
return Super::ChoosePlayerStart_Implementation(player);
}
void AOLSGameMode::FinishRestartPlayer(AController* newPlayer, const FRotator& startRotation)
{
// @TODO: Implement this as well as implement UOLSPlayerSpawningManagerComponent
// if (UOLSPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass<UOLSPlayerSpawningManagerComponent>())
// {
// PlayerSpawningComponent->FinishRestartPlayer(NewPlayer, StartRotation);
// }
Super::FinishRestartPlayer(newPlayer, startRotation);
}
bool AOLSGameMode::PlayerCanRestart_Implementation(APlayerController* player)
{
return ControllerCanRestart(player);
}
void AOLSGameMode::InitGameState()
{
Super::InitGameState();
// Listen for the experience load to complete
UOLSExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass<UOLSExperienceManagerComponent>();
check(ExperienceComponent);
ExperienceComponent->CallOrRegister_OnExperienceLoaded(FOLSExperienceLoadedNativeDelegate::FDelegate::CreateUObject(this, &ThisClass::OnExperienceLoaded));
}
bool AOLSGameMode::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 AOLSGameMode::GenericPlayerInitialization(AController* newPlayer)
{
Super::GenericPlayerInitialization(newPlayer);
OnGameModePlayerInitialized.Broadcast(this, newPlayer);
}
void AOLSGameMode::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
{
// @TODO: Replace this with our custom log.
// 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
{
// @TODO: Replace this with our custom log.
// UE_LOG(LogLyra, Verbose, TEXT("FailedToRestartPlayer(%s) but there's no pawn class so giving up."), *GetPathNameSafe(newPlayer));
}
}
void AOLSGameMode::RequestPlayerRestartNextFrame(AController* controller, bool shouldForceReset)
{
if (shouldForceReset && controller)
{
controller->Reset();
}
if (APlayerController* playerController = Cast<APlayerController>(controller))
{
GetWorldTimerManager().SetTimerForNextTick(playerController, &APlayerController::ServerRestartPlayer_Implementation);
}
// else if (AOLSPlayerBotController* BotController = Cast<ALyraPlayerBotController>(controller))
// {
// GetWorldTimerManager().SetTimerForNextTick(BotController, &ALyraPlayerBotController::ServerRestartController);
// }
}
bool AOLSGameMode::ControllerCanRestart(AController* controller)
{
if (APlayerController* playerController = Cast<APlayerController>(controller))
{
if (!Super::PlayerCanRestart_Implementation(playerController))
{
return false;
}
}
else
{
// Bot version of Super::PlayerCanRestart_Implementation
if ((controller == nullptr) || controller->IsPendingKillPending())
{
return false;
}
}
// @TODO: Implement this after having PlayerSpawningManagerComponent
// if (ULyraPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass<ULyraPlayerSpawningManagerComponent>())
// {
// return PlayerSpawningComponent->ControllerCanRestart(controller);
// }
return true;
}
void AOLSGameMode::OnExperienceLoaded(const UOLSExperienceDefinitionDataAsset* 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* playerController = Cast<APlayerController>(*iterator);
if (playerController && playerController->GetPawn())
{
if (PlayerCanRestart(playerController))
{
RestartPlayer(playerController);
}
}
}
}
bool AOLSGameMode::IsExperienceLoaded() const
{
check(GameState);
UOLSExperienceManagerComponent* experienceComponent = GameState->FindComponentByClass<
UOLSExperienceManagerComponent>();
check(experienceComponent);
return experienceComponent->IsExperienceLoaded();
}
void AOLSGameMode::OnMatchAssignmentGiven(FPrimaryAssetId experienceId, const FString& experienceIdSource)
{
if (experienceId.IsValid())
{
// @TODO: Replace this with our custom.
// UE_LOG(LogLyraExperience, Log, TEXT("Identified experience %s (Source: %s)"), *ExperienceId.ToString(), *ExperienceIdSource);
UOLSExperienceManagerComponent* experienceComponent = GameState->FindComponentByClass<UOLSExperienceManagerComponent>();
check(experienceComponent);
experienceComponent->SetCurrentExperience(experienceId);
}
else
{
// @TODO: Replace this with our custom.
// UE_LOG(LogLyraExperience, Error, TEXT("Failed to identify experience, loading screen will stay up forever"));
}
}
void AOLSGameMode::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
// - Dedicated server
// - Default experience
UWorld* World = GetWorld();
if (!experienceId.IsValid() && UGameplayStatics::HasOption(OptionsString, TEXT("Experience")))
{
const FString ExperienceFromOptions = UGameplayStatics::ParseOption(OptionsString, TEXT("Experience"));
experienceId = FPrimaryAssetId(FPrimaryAssetType(UOLSExperienceDefinitionDataAsset::StaticClass()->GetFName()), FName(*ExperienceFromOptions));
experienceIdSource = TEXT("OptionsString");
}
if (!experienceId.IsValid() && World->IsPlayInEditor())
{
// @TODO: Implement this when UOLSDeveloperSettings is implemented.
// experienceId = GetDefault<UOLSDeveloperSettings>()->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);
if (!experienceId.PrimaryAssetType.IsValid())
{
experienceId = FPrimaryAssetId(FPrimaryAssetType(UOLSExperienceDefinitionDataAsset::StaticClass()->GetFName()), FName(*ExperienceFromCommandLine));
}
experienceIdSource = TEXT("CommandLine");
}
}
// see if the world settings has a default experience
if (!experienceId.IsValid())
{
// @TODO: Implement this when AOLSWorldSettings is implemented.
// if (AOLSWorldSettings* TypedWorldSettings = Cast<ALyraWorldSettings>(GetWorldSettings()))
// {
// experienceId = TypedWorldSettings->GetDefaultGameplayExperience();
// experienceIdSource = TEXT("WorldSettings");
// }
}
UOLSAssetManager& assetManager = UOLSAssetManager::Get();
FAssetData dummy;
if (experienceId.IsValid() && !assetManager.GetPrimaryAssetData(experienceId, /*out*/ dummy))
{
// @TODO: Replace this with our custom.
// UE_LOG(LogLyraExperience, Error, TEXT("EXPERIENCE: Wanted to use %s but couldn't find it, falling back to the default)"), *experienceIding());
experienceId = FPrimaryAssetId();
}
// Final fallback to the default experience
if (!experienceId.IsValid())
{
if (TryDedicatedServerLogin())
{
// This will start to host as a dedicated server
return;
}
//@TODO: Pull this from a config setting or something
experienceId = FPrimaryAssetId(FPrimaryAssetType("LyraExperienceDefinition"), FName("B_LyraDefaultExperience"));
experienceIdSource = TEXT("Default");
}
OnMatchAssignmentGiven(experienceId, experienceIdSource);
}
bool AOLSGameMode::TryDedicatedServerLogin()
{
// Some basic code to register as an active dedicated server, this would be heavily modified by the game
// FString defaultMap = UGameMapsSettings::GetGameDefaultMap();
// UWorld* world = GetWorld();
// UGameInstance* gameInstance = GetGameInstance();
// if (gameInstance && world && world->GetNetMode() == NM_DedicatedServer && world->URL.Map == defaultMap)
// {
// // Only register if this is the default map on a dedicated server
// UCommonUserSubsystem* userSubsystem = gameInstance->GetSubsystem<UCommonUserSubsystem>();
//
// // Dedicated servers may need to do an online login
// userSubsystem->OnUserInitializeComplete.AddDynamic(this, &ThisClass::OnUserInitializedForDedicatedServer);
//
// // There are no local users on dedicated server, but index 0 means the default platform user which is handled by the online login code
// if (!userSubsystem->TryToLoginForOnlinePlay(0))
// {
// OnUserInitializedForDedicatedServer(nullptr, false, FText(), ECommonUserPrivilege::CanPlayOnline, ECommonUserOnlineContext::Default);
// }
//
// return true;
// }
return false;
}
void AOLSGameMode::HostDedicatedServerMatch(ECommonSessionOnlineMode onlineMode)
{
// FPrimaryAssetType UserExperienceType = ULyraUserFacingExperienceDefinition::StaticClass()->GetFName();
//
// // Figure out what UserFacingExperience to load
// FPrimaryAssetId UserExperienceId;
// FString UserExperienceFromCommandLine;
// if (FParse::Value(FCommandLine::Get(), TEXT("UserExperience="), UserExperienceFromCommandLine) ||
// FParse::Value(FCommandLine::Get(), TEXT("Playlist="), UserExperienceFromCommandLine))
// {
// UserExperienceId = FPrimaryAssetId::ParseTypeAndName(UserExperienceFromCommandLine);
// if (!UserExperienceId.PrimaryAssetType.IsValid())
// {
// UserExperienceId = FPrimaryAssetId(FPrimaryAssetType(UserExperienceType), FName(*UserExperienceFromCommandLine));
// }
// }
//
// // Search for the matching experience, it's fine to force load them because we're in dedicated server startup
// ULyraAssetManager& AssetManager = ULyraAssetManager::Get();
// TSharedPtr<FStreamableHandle> Handle = AssetManager.LoadPrimaryAssetsWithType(UserExperienceType);
// if (ensure(Handle.IsValid()))
// {
// Handle->WaitUntilComplete();
// }
//
// TArray<UObject*> UserExperiences;
// AssetManager.GetPrimaryAssetObjectList(UserExperienceType, UserExperiences);
// ULyraUserFacingExperienceDefinition* FoundExperience = nullptr;
// ULyraUserFacingExperienceDefinition* DefaultExperience = nullptr;
//
// for (UObject* Object : UserExperiences)
// {
// ULyraUserFacingExperienceDefinition* UserExperience = Cast<ULyraUserFacingExperienceDefinition>(Object);
// if (ensure(UserExperience))
// {
// if (UserExperience->GetPrimaryAssetId() == UserExperienceId)
// {
// FoundExperience = UserExperience;
// break;
// }
//
// if (UserExperience->bIsDefaultExperience && DefaultExperience == nullptr)
// {
// DefaultExperience = UserExperience;
// }
// }
// }
//
// if (FoundExperience == nullptr)
// {
// FoundExperience = DefaultExperience;
// }
//
// UGameInstance* GameInstance = GetGameInstance();
// if (ensure(FoundExperience && GameInstance))
// {
// // Actually host the game
// UCommonSession_HostSessionRequest* HostRequest = FoundExperience->CreateHostingRequest(this);
// if (ensure(HostRequest))
// {
// HostRequest->OnlineMode = OnlineMode;
//
// // TODO override other parameters?
//
// UCommonSessionSubsystem* SessionSubsystem = GameInstance->GetSubsystem<UCommonSessionSubsystem>();
// SessionSubsystem->HostSession(nullptr, HostRequest);
//
// // This will handle the map travel
// }
// }
}
void AOLSGameMode::OnUserInitializedForDedicatedServer(
const UCommonUserInfo* userInfo,
bool isSuccess,
FText error,
ECommonUserPrivilege requestedPrivilege,
ECommonUserOnlineContext onlineContext)
{
UGameInstance* gameInstance = GetGameInstance();
if (gameInstance)
{
// Unbind
UCommonUserSubsystem* UserSubsystem = gameInstance->GetSubsystem<UCommonUserSubsystem>();
UserSubsystem->OnUserInitializeComplete.RemoveDynamic(this, &ThisClass::OnUserInitializedForDedicatedServer);
// Dedicated servers do not require user login, but some online subsystems may expect it
if (isSuccess && ensure(userInfo))
{
// @TODO: replace this with our custom.
// UE_LOG(LogLyraExperience, Log, TEXT("Dedicated server user login succeeded for id %s, starting online server"), *UserInfo->GetNetId().ToString());
}
else
{
// @TODO: replace this with our custom.
// UE_LOG(LogLyraExperience, Log, TEXT("Dedicated server user login unsuccessful, starting online server as login is not required"));
}
HostDedicatedServerMatch(ECommonSessionOnlineMode::Online);
}
}