2025-01-13 22:36:08 +00:00
// © 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"
2025-01-14 20:35:23 +00:00
# include "Characters/OLSCharacter.h"
2025-01-13 22:36:08 +00:00
# include "DataAssets/OLSExperienceDefinitionDataAsset.h"
# include "DataAssets/OLSPawnDataAsset.h"
# include "GameModes/OLSExperienceManagerComponent.h"
2025-01-14 20:35:23 +00:00
# include "GameModes/OLSGameState.h"
2025-01-13 22:36:08 +00:00
# include "Kismet/GameplayStatics.h"
2025-01-14 20:35:23 +00:00
# include "Player/OLSPlayerController.h"
2025-01-13 22:36:08 +00:00
# include "Player/OLSPlayerState.h"
# include "Systems/OLSAssetManager.h"
2025-01-14 20:35:23 +00:00
# include "Systems/OLSGameSession.h"
# include "UI/OLSHUD.h"
2025-01-13 22:36:08 +00:00
# include UE_INLINE_GENERATED_CPP_BY_NAME(OLSGameMode)
2025-01-14 20:35:23 +00:00
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 ( ) ;
}
2025-01-13 22:36:08 +00:00
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 ) ;
}
}