WIP: Boostrapping NetSlime

- Just a old name for a set of changes to make the game framework hardened for multiplayer as well as some ease of use functionality.
This commit is contained in:
2024-04-23 01:10:02 -04:00
parent 28b1ad19dc
commit 2574960fff
50 changed files with 2109 additions and 119 deletions

View File

@ -1,5 +1,84 @@
#include "GasaGameInstance.h"
#include "Engine/NetDriver.h"
#include "Engine/World.h"
using namespace Gasa;
#pragma region GameFramework
// TODO(Ed): Make a NetLog
void UGasaGameInstance::NotifyGameFrameworkClassReady(EGameFrameworkClassFlag ClassReady)
{
switch (ClassReady)
{
case EGameFrameworkClassFlag::GameMode:
GameFrameworkClassesState |= (uint32)EGameFrameworkClassFlag::GameMode;
NetLog("Gameplay Framework class ready: Game State", ELogV::Log, LogGasaNet );
break;
case EGameFrameworkClassFlag::GameState:
GameFrameworkClassesState |= (uint32)EGameFrameworkClassFlag::GameState;
NetLog("Gameplay Framework class ready: Game State", ELogV::Log, LogGasaNet );
break;
case EGameFrameworkClassFlag::PlayerController:
GameFrameworkClassesState |= (uint32)EGameFrameworkClassFlag::PlayerController;
NetLog("Gameplay Framework class ready: Player Controller", ELogV::Log, LogGasaNet);
break;
case EGameFrameworkClassFlag::PlayerState:
GameFrameworkClassesState |= (uint32)EGameFrameworkClassFlag::PlayerState;
NetLog("Gameplay Framework class ready: Player State", ELogV::Log, LogGasaNet);
break;
case EGameFrameworkClassFlag::Levels:
GameFrameworkClassesState |= (uint32)EGameFrameworkClassFlag::Levels;
NetLog("Gameplay Framework class ready: Levels", ELogV::Log, LogGasaNet);
break;
}
ProcessGameFrameworkState();
}
void UGasaGameInstance::ProcessGameFrameworkState()
{
switch (GameFrameworkState)
{
case EGameFrameworkState::Uninitialized:
{
uint32 InitializedFlags =
(uint32)EGameFrameworkClassFlag::GameState |
(uint32)EGameFrameworkClassFlag::PlayerController |
(uint32)EGameFrameworkClassFlag::PlayerState |
(uint32)EGameFrameworkClassFlag::Levels
;
if (GetWorld()->NetDriver == nullptr || GetWorld()->NetDriver->IsServer())
{
InitializedFlags |= (uint32)EGameFrameworkClassFlag::GameMode;
}
FString MsgGM = "GameMode : " + FString::FromInt( Bitfield_IsSet( GameFrameworkClassesState, scast(uint32, EGameFrameworkClassFlag::GameMode )) );
FString MsgGS = "GameState : " + FString::FromInt( Bitfield_IsSet( GameFrameworkClassesState, scast(uint32, EGameFrameworkClassFlag::GameState )) );
FString MsgPC = "PlayerController: " + FString::FromInt( Bitfield_IsSet( GameFrameworkClassesState, scast(uint32, EGameFrameworkClassFlag::PlayerController ) ));
FString MsgPS = "PlayerState : " + FString::FromInt( Bitfield_IsSet( GameFrameworkClassesState, scast(uint32, EGameFrameworkClassFlag::PlayerState ) ));
FString MsgL = "Levels : " + FString::FromInt( Bitfield_IsSet( GameFrameworkClassesState, scast(uint32, EGameFrameworkClassFlag::Levels ) ));
NetLog(MsgGM, ELogV::Log, LogGasaNet);
NetLog(MsgGS, ELogV::Log, LogGasaNet);
NetLog(MsgPC, ELogV::Log, LogGasaNet);
NetLog(MsgPS, ELogV::Log, LogGasaNet);
NetLog(MsgL, ELogV::Log, LogGasaNet);
if (GameFrameworkClassesState == InitializedFlags)
{
GameFrameworkState = EGameFrameworkState::Initialized;
NetLog("Gameplay Framework initialized");
Event_OnGameFrameworkInitialized.Broadcast();
}
break;
}
}
}
#pragma endregion GameFramework
#pragma region GameInstance
void UGasaGameInstance::Init()
{
Super::Init();
@ -7,5 +86,6 @@ void UGasaGameInstance::Init()
DevOptionsCache.CachedDevOptions();
using namespace Gasa;
Log(FString::Printf(TEXT("UObject Size: %d RT: %d"), sizeof(UObject), UObject::StaticClass()->PropertiesSize ));
NetLog(FString::Printf(TEXT("UObject Size: %d RT: %d"), sizeof(UObject), UObject::StaticClass()->PropertiesSize ));
}
#pragma region GameInstance

View File

@ -1,10 +1,32 @@
#pragma once
#include "GasaCommon.h"
#include "Networking/GasaNetLibrary.h"
#include "GasaDevOptionsCache.h"
#include "Engine/Engine.h"
#include "Engine/GameInstance.h"
#include "GasaGameInstance.generated.h"
UENUM(BlueprintType)
enum class EGameFrameworkClassFlag : uint8
{
None = 0 UMETA(Hidden),
GameMode = 1 << 0,
GameState = 1 << 1,
PlayerController = 1 << 2,
PlayerState = 1 << 3,
Levels = 1 << 4
};
UENUM(BlueprintType)
enum class EGameFrameworkState : uint8
{
Initialized,
Uninitialized
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnGameFrameworkInitializedSig);
UCLASS(Blueprintable)
class GASA_API UGasaGameInstance : public UGameInstance
{
@ -14,6 +36,55 @@ public:
UPROPERTY(VisibleAnywhere, Category="Dev Cache")
FGasaDevOptionsCache DevOptionsCache;
#pragma region GameFramework
UPROPERTY(BlueprintAssignable, Category = "GameFramework")
FOnGameFrameworkInitializedSig Event_OnGameFrameworkInitialized;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GameFramework")
EGameFrameworkState GameFrameworkState;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GameFramework", meta=(Bitmask, BitmaskEnum = EGameFrameworkClassFlag))
int32 GameFrameworkClassesState;
UFUNCTION(BlueprintCallable, Category="GameFramework")
void ClearGameplayFrameworkState() {
Gasa::Log("Clearing game framework state", EGasaVerbosity::Log, LogGasaNet ); // TODO(Ed): Make a default NetLog
GameFrameworkClassesState = scast(int32, EGameFrameworkClassFlag::None);
GameFrameworkState = EGameFrameworkState::Uninitialized;
}
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GameFramework")
FORCEINLINE bool IsGameFrameworkInitialized() { return GameFrameworkState == EGameFrameworkState::Initialized; }
UFUNCTION(BlueprintCallable, Category="GameFramework")
void NotifyGameFrameworkClassReady(EGameFrameworkClassFlag ClassReady);
UFUNCTION(BlueprintCallable, Category = "GameFramework", meta=(BlueprintProtected))
void ProcessGameFrameworkState();
#pragma endregion GameFramework
#pragma region NetSlime
// NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp
FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); }
FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); }
FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); }
FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); }
FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); }
FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); }
FORCEINLINE void NetLog(
FString Message,
EGasaVerbosity Verbosity = EGasaVerbosity::Log,
FLogCategoryBase& Category = LogGasaNet,
bool DumpStack = false,
int32 Line = __builtin_LINE(),
ANSICHAR const* File = __builtin_FILE(),
ANSICHAR const* Func = __builtin_FUNCTION()
)
{
Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func );
}
#pragma endregion NetSlime
#pragma region GameInstance
void Init() override;
#pragma endregion GameInstance

View File

@ -1,2 +1,454 @@
#include "GasaGameMode.h"
#include "GasaGameInstance.h"
#include "GasaGameState.h"
#include "GasaPlayerController.h"
#include "GasaPlayerState.h"
#include "Engine/Player.h"
#include "GameFramework/GameSession.h"
#include "GameFramework/GameState.h"
#include "GameFramework/PlayerState.h"
#include "OnlineSubsystem.h"
using namespace Gasa;
#pragma region Game Framework
void AGasaGameMode::OnGameFrameworkInitialized()
{
NetLog("OnGameFrameworkInitialized");
BP_OnGameFrameworkInitialized();
if (MatchState == MatchState::WaitingToStart && ReadyToStartMatch())
StartMatch();
}
void AGasaGameMode::OwningClient_OnGameFrameworkInitialized(AGasaPlayerController* PC)
{
NetLog("OwningClient_OnGameFrameworkInitialized");
HandleStartingNewPlayer(PC);
}
#pragma endregion Game Framework
#pragma region GameMode
void AGasaGameMode::HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer)
{
Super::HandleStartingNewPlayer_Implementation(NewPlayer);
if (NewPlayer)
NewPlayer->bBlockInput = false;
}
bool AGasaGameMode::ReadyToStartMatch_Implementation()
{
if ( ! GetGameInstance<UGasaGameInstance>()->IsGameFrameworkInitialized())
{
return false;
}
// Super::ReadyToStartMatch();
{
// If bDelayed Start is set, wait for a manual match start
if (bDelayedStart)
{
return false;
}
// By default start when we have > 0 players
if (GetMatchState() == MatchState::WaitingToStart)
{
if (NumPlayers + NumBots > 0)
{
return true;
}
}
return false;
}
}
#pragma endregion GameMode
#pragma region GameModeBase
void AGasaGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
NetLog("EndPlay");
}
void AGasaGameMode::GenericPlayerInitialization(AController* C)
{
NetLog("GenericPlayerInitialization: " + C->GetName());
// AGameMode::GenericPlayerInitialization(C);
{
APlayerController* PC = Cast<APlayerController>(C);
if (PC != nullptr)
{
// Moved to: void AGasaGameMode::SetPlayerDefaults(APawn* PlayerPawn)
// InitializeHUDForPlayer(Cast<APlayerController>(C));
// Notify the game that we can now be muted and mute others
UpdateGameplayMuteList(PC);
if (GameSession != nullptr)
{
// Tell the player to enable voice by default or use the push to talk method
PC->ClientEnableNetworkVoice(!GameSession->RequiresPushToTalk());
}
ReplicateStreamingStatus(PC);
bool HidePlayer = false, HideHUD = false, DisableMovement = false, DisableTurning = false;
// Check to see if we should start in cinematic mode (matinee movie capture)
if (ShouldStartInCinematicMode(PC, HidePlayer, HideHUD, DisableMovement, DisableTurning))
{
PC->SetCinematicMode(true, HidePlayer, HideHUD, DisableMovement, DisableTurning);
}
}
}
}
TSubclassOf<APlayerController> AGasaGameMode::GetPlayerControllerClassToSpawnForSeamlessTravel(APlayerController* PreviousPlayerController)
{
NetLog("GetPlayerControllerClassToSpawnForSeamlessTravel: " + PreviousPlayerController->GetName());
return Super::GetPlayerControllerClassToSpawnForSeamlessTravel(PreviousPlayerController);
}
void AGasaGameMode::HandleSeamlessTravelPlayer(AController*& Controller)
{
NetLog("HandleSeamlessTravelPlayer: " + Controller->GetName());
// Super::HandleSeamlessTravelPlayer( C );
UE_LOG(LogGameMode, Log, TEXT(">> GameMode::HandleSeamlessTravelPlayer: %s "), * Controller->GetName());
APlayerController* PC = Cast<APlayerController>(Controller);
UClass* PCClassToSpawn = GetPlayerControllerClassToSpawnForSeamlessTravel(PC);
// FUniqueNetIdRepl StoredNativePlatformUniqueNetId = C->GetPlayerState<AGasaPlayerState>()->NativePlatformUniqueNetId;
if (PC && PC->GetClass() != PCClassToSpawn)
{
if (PC->Player != nullptr)
{
// We need to spawn a new PlayerController to replace the old one
APlayerController* const NewPC = SpawnPlayerControllerCommon(PC->IsLocalPlayerController()
? ROLE_SimulatedProxy
: ROLE_AutonomousProxy, PC->GetFocalLocation(), PC->GetControlRotation(), PCClassToSpawn);
if (NewPC == nullptr)
{
NetLog(FString::Printf(
TEXT("Failed to spawn new PlayerController for %s (old class %s)"), *PC->GetHumanReadableName(), *PC->GetClass()->GetName())
, ELogV::Warning
);
PC->Destroy();
return;
}
else
{
PC->SeamlessTravelTo(NewPC);
NewPC->SeamlessTravelFrom(PC);
SwapPlayerControllers(PC, NewPC);
PC = NewPC;
Controller = NewPC;
}
}
else
{
PC->Destroy();
}
}
else
{
// clear out data that was only for the previous game
Controller->PlayerState->Reset();
// create a new PlayerState and copy over info; this is necessary because the old GameMode may have used a different PlayerState class
APlayerState* OldPlayerState = Controller->PlayerState;
Controller->InitPlayerState();
OldPlayerState->SeamlessTravelTo( Controller->PlayerState);
// we don"t need the old PlayerState anymore
//@fixme: need a way to replace PlayerStates that doesn't cause incorrect "player left the game"/"player entered the game" messages
OldPlayerState->Destroy();
}
InitSeamlessTravelPlayer(Controller);
// Initialize hud and other player details, shared with PostLogin
GenericPlayerInitialization(Controller);
if (AGasaGameState* GS = GetGameState<AGasaGameState>())
{
#if 0
UGasaGameInstance* GI = GetGameInstance<UGasaGameInstance>();
int32 NumConnections = GI->SessionSettings.bPublicGame
? GI->SessionSettings.PublicConnections
: GI->SessionSettings.PrivateConnections;
if (GS->OnlinePlayers.Num() < NumConnections)
GS->OnlinePlayers.Init( nullptr, NumConnections );
for (AGasaPlayerState* & PS : GS->OnlinePlayers)
{
if (PS == nullptr)
{
PS = C->GetPlayerState<ASlipgatePlayerState>();
PS->SgID = GS->OnlinePlayers.Find( PS );
break;
}
}
#endif
}
// Controller->GetPlayerState<AGasaPlayerState>()->NativePlatformUniqueNetId = StoredNativePlatformUniqueNetId;
NetLog(FString::Printf(TEXT("HandleSeamlessTravelPlayer: %s"), * Controller->GetName()) );
#if 0
if (PC)
PC->Event_NetOwner_OnGameplayFrameworkInitialized.AddDynamic(this, &ThisClass::OwningClient_OnGameFrameworkInitialized);
#endif
}
void AGasaGameMode::InitializeHUDForPlayer_Implementation(APlayerController* NewPlayer)
{
// Super::InitializeHUDForPlayer_Implementation(NewPlayer);
NewPlayer->ClientSetHUD(HUDClass);
}
void AGasaGameMode::InitSeamlessTravelPlayer(AController* NewController)
{
if (NewController)
NewController->bBlockInput = true;
// GameMode::InitSeamlessTravelPlayer(NewController);
{
// AGameModeBase::InitSeamlessTravelPlayer(NewController);
{
APlayerController* NewPC = Cast<APlayerController>(NewController);
FString ErrorMessage;
if (!UpdatePlayerStartSpot(NewController, TEXT(""), ErrorMessage))
NetLog(FString::Printf( TEXT("InitSeamlessTravelPlayer: %s"), *ErrorMessage), ELogV::Warning);
if (NewPC != nullptr)
{
NewPC->PostSeamlessTravel();
if (MustSpectate(NewPC))
{
NewPC->StartSpectatingOnly();
}
else
{
NewPC->bPlayerIsWaiting = true;
NewPC->ChangeState(NAME_Spectating);
NewPC->ClientGotoState(NAME_Spectating);
}
}
}
APlayerController* NewPC = Cast<APlayerController>(NewController);
if (NewPC != nullptr)
{
SetSeamlessTravelViewTarget(NewPC);
if (!MustSpectate(NewPC))
{
NumPlayers++;
NumTravellingPlayers--;
}
}
else
{
NumBots++;
}
}
NetLog("InitSeamlessTravelPlayer: " + NewController->GetName());
}
void AGasaGameMode::HandleMatchAborted()
{
Super::HandleMatchAborted();
NetLog("HandleMatchAborted");
}
void AGasaGameMode::Logout(AController* Exiting)
{
Super::Logout(Exiting);
Event_OnLogout.Broadcast(Cast<AGasaPlayerController>(Exiting));
NetLog("User Logged out: " + Exiting->GetName());
if (AGasaGameState* GS = Cast<AGasaGameState>(GetWorld()->GetGameState()))
{
#if 0
int32 Index = GS->OnlinePlayers.Find(Exiting->GetPlayerState<AGasaGameState>());
if (Index == INDEX_NONE)
{
NetLog("Could not find exiting player state in online players!", ELogV::Error);
return;
}
GS->OnlinePlayers[Index] = nullptr;
#endif
#if 0
IOnlineSessionPtr const SessionInterface = Online::GetSessionInterface(GetWorld());
if ( ! SessionInterface.IsValid())
{
NetLog("DestoryCurrentSession: Could not get the session interface.", ELogV::Warning);
return;
}
#endif
}
}
void AGasaGameMode::PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)
{
Super::PreLogin(Options, Address, UniqueId, ErrorMessage);
// TODO(Ed): Refuse players if the server is full.
}
void AGasaGameMode::PostLogin(APlayerController* NewPlayer)
{
// AGameMode::PostLogin(NewPlayer);
{
UWorld* World = GetWorld();
// Update player count
if (MustSpectate(NewPlayer))
NumSpectators++;
else if (World->IsInSeamlessTravel() || NewPlayer->HasClientLoadedCurrentWorld())
NumPlayers++;
else
NumTravellingPlayers++;
// Save network address for re-associating with reconnecting player, after stripping out port number
FString Address = NewPlayer->GetPlayerNetworkAddress();
int32 Pos = Address.Find(TEXT(":"), ESearchCase::CaseSensitive);
NewPlayer->PlayerState->SavedNetworkAddress = (Pos > 0) ? Address.Left(Pos) : Address;
// Check if this player is reconnecting and already has PlayerState
FindInactivePlayer(NewPlayer);
}
// AGameModeBase::PostLogin(NewPlayer)
{
// Runs shared initialization that can happen during seamless travel as well
GenericPlayerInitialization(NewPlayer);
// Perform initialization that only happens on initially joining a server
NewPlayer->ClientCapBandwidth(NewPlayer->Player->CurrentNetSpeed);
if (MustSpectate(NewPlayer))
{
NewPlayer->ClientGotoState(NAME_Spectating);
}
else
{
// If NewPlayer is not only a spectator and has a valid ID, add him as a user to the replay.
FUniqueNetIdRepl const&
NewPlayerStateUniqueId = NewPlayer->PlayerState->GetUniqueId();
if (NewPlayerStateUniqueId.IsValid())
{
GetGameInstance()->AddUserToReplay(NewPlayerStateUniqueId.ToString());
}
}
if (GameSession)
{
GameSession->PostLogin(NewPlayer);
}
}
if (AGasaGameState* GS = GetGameState<AGasaGameState>())
{
UGasaGameInstance* GI = GetGameInstance<UGasaGameInstance>();
// int32 numconnections = gi->sessionsettings.bpublicgame
// ? GI->SessionSettings.PublicConnections : GI->SessionSettings.PrivateConnections;
#if 0
if (GS->OnlinePlayers.Num() < NumConnections)
{
GS->OnlinePlayers.Init( nullptr, NumConnections );
}
for (AGasaPlayerState* & PS : GS->OnlinePlayers)
{
if (PS == nullptr)
{
PS = NewPlayer->GetPlayerState<AGasaPlayerState>();
PS->GasaID = GS->OnlinePlayers.Find( PS );
break;
}
}
#endif
}
// cont. AGameModeBase::PostLogin(NewPlayer)
{
// Notify Blueprints that a new player has logged in. Calling it here, because this is the first time that the PlayerController can take RPCs
DispatchPostLogin(NewPlayer);
}
AGasaPlayerController* PC = Cast<AGasaPlayerController>(NewPlayer);
// if (PC)
// PC->Event_OnNetOwner_GameplayFrameworkInitialized.AddDynamic(this, &ThisClass::OwningClient_OnGameFrameworkInitialized);
}
void AGasaGameMode::PostSeamlessTravel()
{
Super::PostSeamlessTravel();
NetLog("PostSeamlessTravel");
}
void AGasaGameMode::SetPlayerDefaults(APawn* PlayerPawn)
{
InitializeHUDForPlayer(Cast<APlayerController>(PlayerPawn->GetController()));
// Super::SetPlayerDefaults(PlayerPawn);
{
PlayerPawn->SetPlayerDefaults();
}
}
void AGasaGameMode::SetSeamlessTravelViewTarget(APlayerController* PC)
{
Super::SetSeamlessTravelViewTarget(PC);
NetLog("SetSeamlessTravelViewTarget");
}
void AGasaGameMode::StartPlay()
{
if (MatchState == MatchState::EnteringMap)
SetMatchState(MatchState::WaitingToStart);
// Start match is deferred until the framework is considered initialized.
NetLog("StartPlay");
UGasaGameInstance* GI = GetGameInstance<UGasaGameInstance>();
GI->Event_OnGameFrameworkInitialized.AddDynamic(this, &ThisClass::OnGameFrameworkInitialized);
GI->NotifyGameFrameworkClassReady(EGameFrameworkClassFlag::GameMode);
// Not called yet, will wait for initialization of the framework.
//Super::StartPlay();
}
void AGasaGameMode::StartToLeaveMap()
{
NetLog("StartToLeaveMap");
Super::StartToLeaveMap();
}
void AGasaGameMode::SwapPlayerControllers(APlayerController* OldPC, APlayerController* NewPC)
{
NetLog("SwapPlayerControllers");
NetLog("Old: " + OldPC->GetName());
NetLog("New: " + NewPC->GetName());
Super::SwapPlayerControllers(OldPC, NewPC);
}
APlayerController* AGasaGameMode::SpawnPlayerControllerCommon(ENetRole InRemoteRole, FVector const& SpawnLocation, FRotator const& SpawnRotation,
TSubclassOf<APlayerController> InPlayerControllerClass)
{
NetLog("SpawnPlayerControllerCommon");
return Super::SpawnPlayerControllerCommon(InRemoteRole, SpawnLocation, SpawnRotation, InPlayerControllerClass);
}
#pragma endregion GameModeBase

View File

@ -1,28 +1,102 @@
#pragma once
#pragma once
#include "GameFramework/GameMode.h"
#include "GasaCommon.h"
#include "Networking/GasaNetLibrary_Inlines.h"
#include "Engine/Engine.h"
#include "GasaGameMode.generated.h"
UCLASS(Blueprintable)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnLogoutSig, AGasaPlayerController*, PC);
UCLASS( Blueprintable )
class GASA_API AGasaGameMode : public AGameMode
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintAssignable)
FOnLogoutSig Event_OnLogout;
#pragma region GameFramework
UFUNCTION()
void OnGameFrameworkInitialized();
UFUNCTION()
void OwningClient_OnGameFrameworkInitialized(AGasaPlayerController* PC);
UFUNCTION(BlueprintCallable, meta=(DisplayName = "On Game Framework Initialized"))
void BP_OnGameFrameworkInitialized();
#pragma endregion GameFramework
#pragma region NetSlime
// NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp
FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); }
FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); }
FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); }
FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); }
FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); }
FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); }
FORCEINLINE void NetLog(
FString Message,
EGasaVerbosity Verbosity = EGasaVerbosity::Log,
FLogCategoryBase& Category = LogGasaNet,
bool DumpStack = false,
int32 Line = __builtin_LINE(),
ANSICHAR const* File = __builtin_FILE(),
ANSICHAR const* Func = __builtin_FUNCTION()
)
{
Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func );
}
#pragma endregion NetSlime
#pragma region GameMode
void HandleMatchAborted() override;
void HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer) override;
bool ReadyToStartMatch_Implementation() override;
#pragma endregion GameMode
#pragma region GameModeBase
void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
void GenericPlayerInitialization(AController* C) override;
TSubclassOf<APlayerController> GetPlayerControllerClassToSpawnForSeamlessTravel(APlayerController* PreviousPlayerController) override;
void HandleSeamlessTravelPlayer(AController*& C) override;
void InitializeHUDForPlayer_Implementation(APlayerController* NewPlayer) override;
void InitSeamlessTravelPlayer(AController* NewController) override;
void Logout(AController* Exiting) override;
void PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) override;
void PostLogin(APlayerController* NewPlayer) override;
void PostSeamlessTravel() override;
void SetPlayerDefaults(APawn* PlayerPawn) override;
void SetSeamlessTravelViewTarget(APlayerController* PC) override;
void StartPlay() override;
void StartToLeaveMap() override;
void SwapPlayerControllers(APlayerController* OldPC, APlayerController* NewPC) override;
APlayerController* SpawnPlayerControllerCommon(ENetRole InRemoteRole, FVector const& SpawnLocation, FRotator const& SpawnRotation, TSubclassOf<APlayerController> InPlayerControllerClass) override;
#pragma endregion GameModeBase
};
namespace Gasa
{
inline
AGasaGameMode* GetGameMode(UObject* Context)
inline AGasaGameMode* GetGameMode( UObject* Context )
{
UWorld* World = GEngine->GetWorldFromContextObject(Context, EGetWorldErrorMode::LogAndReturnNull);
if (World == nullptr)
UWorld* World = GEngine->GetWorldFromContextObject( Context, EGetWorldErrorMode::LogAndReturnNull );
if ( World == nullptr )
{
Log("World is null... are you running in a proper context?", ELogV::Error);
Log( "World is null... are you running in a proper context?", ELogV::Error );
return nullptr;
}
return Cast<AGasaGameMode>(World->GetAuthGameMode());
return Cast<AGasaGameMode>( World->GetAuthGameMode() );
}
}

View File

@ -2,6 +2,10 @@
#include "CogAll.h"
#include "CogWindowManager.h"
#include "GasaPlayerState.h"
#include "GasaGameInstance.h"
#include "Net/UnrealNetwork.h"
using namespace Gasa;
AGasaGameState::AGasaGameState()
{
@ -9,11 +13,92 @@ AGasaGameState::AGasaGameState()
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.SetTickFunctionEnable(true);
PrimaryActorTick.bStartWithTickEnabled = true;
// Replication
bReplicates = true;
bNetLoadOnClient = false;
NetDormancy = DORM_Awake;
NetCullDistanceSquared = NetCullDist_Default;
NetUpdateFrequency = 10.0f;
MinNetUpdateFrequency = 1.0f;
NetPriority = 5.0f;
}
#pragma region GameState
#pragma region GameFramework
void AGasaGameState::OnGameFrameworkInitialized()
{
NetLog("Received gameplay framework initialization.");
if (IsServer())
{
if (PlayerArray.Num() > 0)
{
ListenServerHost = Cast<AGasaPlayerState>(PlayerArray[0]);
}
else
{
NetLog("Was not able to assign HostingPlayer!", ELogV::Error);
}
}
BP_OnGameFrameworkInitialized();
}
#pragma endregion GameFramework
#pragma region Networking
void AGasaGameState::Client_OnRep_OnlinePlayers()
{
}
#pragma endregion Networking
#pragma region Seamless Travel
void AGasaGameState::Multicast_R_NotifySeamlessTravelEnd_Implementation()
{
NetLog("Multicast_R_NotifySeamlessTravelEnd_Implementation");
BP_Event_OnSeamlessTravelEnd.Broadcast();
Event_OnSeamlessTravelEnd.Broadcast();
}
#pragma endregion Seamless Travel
#pragma region GameStateBase
void AGasaGameState::HandleBeginPlay()
{
Super::HandleBeginPlay();
NetLog("HandleBeginPlay: Directly called from GM");
}
void AGasaGameState::SeamlessTravelTransitionCheckpoint(bool bToTransitionMap)
{
Super::SeamlessTravelTransitionCheckpoint(bToTransitionMap);
NetLog("SeamlessTravelTransitionCheckpoint");
NetLog(FString("ToTransitionMap: ") + FString(bToTransitionMap ? "true" : "false"));
if (bToTransitionMap)
{
Event_OnSeamlessTravelStart.Broadcast();
}
else
{
Multicast_R_NotifySeamlessTravelEnd();
}
}
#pragma endregion GameStateBase
#pragma region Actor
void AGasaGameState::BeginPlay()
{
Super::BeginPlay();
NetLog("BeginPlay");
// Notified as initialized here as any possible components should also be initialized by this point.
UGasaGameInstance*
GI = GetGameInstance<UGasaGameInstance>();
GI->Event_OnGameFrameworkInitialized.AddDynamic(this, & ThisClass::OnGameFrameworkInitialized);
GI->NotifyGameFrameworkClassReady(EGameFrameworkClassFlag::GameState);
#if ENABLE_COG
CogWindowManager = NewObject<UCogWindowManager>(this);
CogWindowManagerRef = CogWindowManager;
@ -23,6 +108,28 @@ void AGasaGameState::BeginPlay()
#endif //ENABLE_COG
}
void AGasaGameState::PostInitializeComponents()
{
NetLog("PostInitializeComponents");
Super::PostInitializeComponents();
if ( ! GetWorld()->IsEditorWorld() && IsServer())
{
OnlinePlayers.Empty();
#if 0
const auto GI = Cast<UGasaGameInstance>(GetGameInstance());
if (GI != nullptr)
{
int32 NumConnections = GI->SessionSettings.bPublicGame
? GI->SessionSettings.PublicConnections
: GI->SessionSettings.PrivateConnections;
OnlinePlayers.Init(nullptr, NumConnections);
}
#endif
}
}
void AGasaGameState::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
@ -31,4 +138,14 @@ void AGasaGameState::Tick(float DeltaSeconds)
CogWindowManager->Tick(DeltaSeconds);
#endif //ENABLE_COG
}
#pragma endregion GameState
#pragma endregion Actor
#pragma region UObject
void AGasaGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AGasaGameState, ListenServerHost);
DOREPLIFETIME(AGasaGameState, OnlinePlayers);
}
#pragma endregion UObject

View File

@ -1,46 +1,116 @@
#pragma once
#include "GameFramework/GameState.h"
#include "GasaCommon.h"
#include "Engine/Engine.h"
#include "Networking/GasaNetLibrary.h"
#include "GasaGameState.generated.h"
UCLASS(Blueprintable)
DECLARE_MULTICAST_DELEGATE( FOnTravelDelegate );
DECLARE_DYNAMIC_MULTICAST_DELEGATE( FOnTravelSig );
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayerCharacterReadySig, APlayerCharacter*, Character);
UCLASS( Blueprintable )
class GASA_API AGasaGameState : public AGameState
{
GENERATED_BODY()
public:
#pragma region Cog
// To make sure it doesn't get garbage collected.
UPROPERTY()
TObjectPtr<UObject> CogWindowManagerRef;
// To make sure it doesn't get garbage collected.
UPROPERTY()
TObjectPtr<UObject> CogWindowManagerRef;
#if ENABLE_COG
TObjectPtr<UCogWindowManager> CogWindowManager;
#endif // ENABLE_COG
#endif
// ENABLE_COG
#pragma endregion Cog
AGasaGameState();
#pragma region GameFramework
UFUNCTION()
void OnGameFrameworkInitialized();
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, meta=(DisplayName = "Game Framework Initialized"))
void BP_OnGameFrameworkInitialized();
#pragma endregion GameFramework
#pragma region Networking
UPROPERTY(Replicated, BlueprintReadOnly)
AGasaPlayerState* ListenServerHost;
UPROPERTY(ReplicatedUsing = "Client_OnRep_OnlinePlayers", BlueprintReadOnly)
TArray<AGasaPlayerState> OnlinePlayers;
UFUNCTION()
void Client_OnRep_OnlinePlayers();
#pragma endregion Networking
#pragma region Seamless Travel
UPROPERTY(BlueprintAssignable)
FOnTravelSig Event_OnSeamlessTravelStart;
UPROPERTY(BlueprintAssignable, meta=(DisplayName="Event: On Seamless Travel End"))
FOnTravelSig BP_Event_OnSeamlessTravelEnd;
FOnTravelDelegate Event_OnSeamlessTravelEnd;
UFUNCTION(NetMulticast, Reliable)
void Multicast_R_NotifySeamlessTravelEnd();
#pragma endregion Seamless Travel
#pragma region NetSlime
// NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp
FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); }
FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); }
FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); }
FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); }
FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); }
FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); }
FORCEINLINE void NetLog(
FString Message,
EGasaVerbosity Verbosity = EGasaVerbosity::Log,
FLogCategoryBase& Category = LogGasaNet,
bool DumpStack = false,
int32 Line = __builtin_LINE(),
ANSICHAR const* File = __builtin_FILE(),
ANSICHAR const* Func = __builtin_FUNCTION()
)
{
Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func );
}
#pragma endregion NetSlime
#pragma region GameState
void BeginPlay() override;
void HandleBeginPlay() override;
#pragma endregion GameState
void Tick(float DeltaSeconds) override;
#pragma endregion GameState
#pragma region GameStateBase
void SeamlessTravelTransitionCheckpoint(bool bToTransitionMap) override;
#pragma endregion GameStateBase
#pragma region Actor
void BeginPlay() override;
void PostInitializeComponents() override;
void Tick( float DeltaSeconds ) override;
#pragma endregion Actor
#pragma region UObject
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
#pragma endregion UObject
};
namespace Gasa
{
inline
AGasaGameState* GetGameState(UObject* Context)
inline AGasaGameState* GetGameState( UObject* Context )
{
UWorld* World = GEngine->GetWorldFromContextObject(Context, EGetWorldErrorMode::LogAndReturnNull);
if (World == nullptr)
UWorld* World = GEngine->GetWorldFromContextObject( Context, EGetWorldErrorMode::LogAndReturnNull );
if ( World == nullptr )
{
Log("World is null... are you running in a proper context?", ELogV::Error);
Log( "World is null... are you running in a proper context?", ELogV::Error );
return nullptr;
}
return Cast<AGasaGameState>(World->GetGameState());
return Cast<AGasaGameState>( World->GetGameState() );
}
}

View File

@ -1,11 +1,39 @@
#include "GasaLevelScriptActor.h"
#include "GasaDevOptions.h"
#include "GasaGameInstance.h"
#include "Engine/PostProcessVolume.h"
#include "Kismet/GameplayStatics.h"
#include "Materials/MaterialInstance.h"
#include "Materials/MaterialInstanceDynamic.h"
using namespace Gasa;
#pragma region Game Framework
AGasaLevelScriptActor::AGasaLevelScriptActor()
{
// Replication
bReplicates = true;
bNetLoadOnClient = true;
NetDormancy = DORM_Awake;
NetCullDistanceSquared = NetCullDist_Default;
NetUpdateFrequency = 10.0f;
MinNetUpdateFrequency = 1.0f;
NetPriority = 1.0f;
}
void AGasaLevelScriptActor::OnGameFrameworkInitialized()
{
NetLog("Received game framework initialization.");
BP_OnGameFrameworkInitialized();
}
#pragma endregion Game Framework
#pragma region Actor
void AGasaLevelScriptActor::BeginPlay()
{
Super::BeginPlay();
NetLog("BeginPlay");
using namespace Gasa;
@ -21,4 +49,12 @@ void AGasaLevelScriptActor::BeginPlay()
PPV->Settings.WeightedBlendables.Array[0].Object = MID;
break;
}
UGasaGameInstance* GI = GetGameInstance<UGasaGameInstance>();
if(GI)
GI->Event_OnGameFrameworkInitialized.AddUniqueDynamic(this, & ThisClass::OnGameFrameworkInitialized);
if (!bOverrideGameplayFrameworkReady)
GI->NotifyGameFrameworkClassReady(EGameFrameworkClassFlag::Levels);
}
#pragma endregion Actor

View File

@ -1,8 +1,9 @@
#pragma once
#include "GasaCommon.h"
#include "Engine/Engine.h"
#include "Engine/LevelScriptActor.h"
#include "GasaCommon.h"
#include "Networking/GasaNetLibrary.h"
#include "GasaLevelScriptActor.generated.h"
@ -14,6 +15,41 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Post Process")
TObjectPtr<APostProcessVolume> GlobalPPV;
AGasaLevelScriptActor();
#pragma region GameFramework
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
bool bOverrideGameplayFrameworkReady = false;
UFUNCTION()
void OnGameFrameworkInitialized();
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, meta=(DisplayName="On Game Framework Initialized"))
void BP_OnGameFrameworkInitialized();
#pragma endregion GameFramework
#pragma region NetSlime
// NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp
FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); }
FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); }
FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); }
FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); }
FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); }
FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); }
FORCEINLINE void NetLog(
FString Message,
EGasaVerbosity Verbosity = EGasaVerbosity::Log,
FLogCategoryBase& Category = LogGasaNet,
bool DumpStack = false,
int32 Line = __builtin_LINE(),
ANSICHAR const* File = __builtin_FILE(),
ANSICHAR const* Func = __builtin_FUNCTION()
)
{
Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func );
}
#pragma endregion NetSlime
#pragma region Actor
void BeginPlay() override;
#pragma region endActor

View File

@ -155,19 +155,22 @@ void AGasaPlayerController::BeginPlay()
{
Super::BeginPlay();
check(IMC);
UEnhancedInputLocalPlayerSubsystem*
EILP_Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer());
check(EILP_Subsystem);
EILP_Subsystem->AddMappingContext(IMC, 0);
if (IsLocalController())
{
bShowMouseCursor = true;
DefaultMouseCursor = EMouseCursor::Default;
FInputModeGameAndUI MouseMode;
MouseMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
MouseMode.SetHideCursorDuringCapture(false);
SetInputMode(MouseMode);
check(IMC);
UEnhancedInputLocalPlayerSubsystem*
EILP_Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer());
check(EILP_Subsystem);
EILP_Subsystem->AddMappingContext(IMC, 0);
{
bShowMouseCursor = true;
DefaultMouseCursor = EMouseCursor::Default;
FInputModeGameAndUI MouseMode;
MouseMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
MouseMode.SetHideCursorDuringCapture(false);
SetInputMode(MouseMode);
}
}
}

View File

@ -1,7 +1,9 @@
#pragma once
#include "GasaCommon.h"
#include "Engine/Engine.h"
#include "GameFramework/PlayerController.h"
#include "Networking/GasaNetLibrary.h"
#include "GasaPlayerController.generated.h"
@ -51,12 +53,33 @@ public:
AGasaPlayerController();
inline AGasaPlayerState* GetPlayerState();
#pragma region NetSlime
// NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp
FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); }
FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); }
FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); }
FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); }
FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); }
FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); }
FORCEINLINE void NetLog(
FString Message,
EGasaVerbosity Verbosity = EGasaVerbosity::Log,
FLogCategoryBase& Category = LogGasaNet,
bool DumpStack = false,
int32 Line = __builtin_LINE(),
ANSICHAR const* File = __builtin_FILE(),
ANSICHAR const* Func = __builtin_FUNCTION()
)
{
Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func );
}
#pragma endregion NetSlime
#pragma region PlayerController
void SpawnDefaultHUD() override;
void OnPossess(APawn* InPawn) override;
void OnUnPossess() override;
void PlayerTick(float DeltaTime) override;

View File

@ -5,6 +5,8 @@
AGasaPlayerState::AGasaPlayerState()
{
bAutoAbilitySystem = true;
AbilitySystem = CreateDefaultSubobject<UGasaAbilitySystemComp>("Ability System");
AbilitySystem->SetIsReplicated(true);
AbilitySystem->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);

View File

@ -5,6 +5,7 @@
#include "GameFramework/PlayerState.h"
#include "GasaCommon.h"
#include "Networking/GasaNetLibrary.h"
#include "GasaPlayerState.generated.h"
@ -16,7 +17,7 @@ class GASA_API AGasaPlayerState : public APlayerState
public:
#pragma region Ability System
UPROPERTY(EditAnywhere, Category="Ability System")
bool bAutoAbilitySystem = true;
bool bAutoAbilitySystem;
UPROPERTY(EditAnywhere, Category="Ability System")
TObjectPtr<UAbilitySystemComponent> AbilitySystem;
@ -27,6 +28,28 @@ public:
AGasaPlayerState();
#pragma region NetSlime
// NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp
FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); }
FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); }
FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); }
FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); }
FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); }
FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); }
FORCEINLINE void NetLog(
FString Message,
EGasaVerbosity Verbosity = EGasaVerbosity::Log,
FLogCategoryBase& Category = LogGasaNet,
bool DumpStack = false,
int32 Line = __builtin_LINE(),
ANSICHAR const* File = __builtin_FILE(),
ANSICHAR const* Func = __builtin_FUNCTION()
)
{
Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func );
}
#pragma endregion NetSlime
#pragma region IAbilitySystem
FORCEINLINE UAttributeSet* GetAttributes() { return Attributes; }
FORCEINLINE UAbilitySystemComponent* GetAbilitySystemComponent() const override { return AbilitySystem; }