Added CommonUser Plugins.

Implemented GameplayStackTags
This commit is contained in:
LongLy 2025-01-13 15:36:08 -07:00
parent 313cf68f61
commit 7415b74e8f
28 changed files with 7051 additions and 27 deletions

View File

@ -0,0 +1,38 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "CommonUser",
"Description": "Provides gameplay code and blueprint wrappers for online and platform operations.",
"Category": "Gameplay",
"CreatedBy": "Epic Games, Inc.",
"CreatedByURL": "https://www.epicgames.com",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": false,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "CommonUser",
"Type": "Runtime",
"LoadingPhase": "Default"
}
],
"Plugins": [
{
"Name": "OnlineSubsystem",
"Enabled": true
},
{
"Name": "OnlineSubsystemUtils",
"Enabled": true
},
{
"Name": "OnlineServices",
"Enabled": true
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,71 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class CommonUser : ModuleRules
{
public CommonUser(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
bool bUseOnlineSubsystemV1 = true;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreOnline",
"GameplayTags",
"OnlineSubsystemUtils",
// ... add other public dependencies that you statically link with here ...
}
);
if (bUseOnlineSubsystemV1)
{
PublicDependencyModuleNames.Add("OnlineSubsystem");
}
else
{
PublicDependencyModuleNames.Add("OnlineServicesInterface");
}
PublicDefinitions.Add("COMMONUSER_OSSV1=" + (bUseOnlineSubsystemV1 ? "1" : "0"));
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreOnline",
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"ApplicationCore",
"InputCore",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@ -0,0 +1,105 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AsyncAction_CommonUserInitialize.h"
#include "GenericPlatform/GenericPlatformInputDeviceMapper.h"
#include "TimerManager.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AsyncAction_CommonUserInitialize)
UAsyncAction_CommonUserInitialize* UAsyncAction_CommonUserInitialize::InitializeForLocalPlay(UCommonUserSubsystem* Target, int32 LocalPlayerIndex, FInputDeviceId PrimaryInputDevice, bool bCanUseGuestLogin)
{
if (!PrimaryInputDevice.IsValid())
{
// Set to default device
PrimaryInputDevice = IPlatformInputDeviceMapper::Get().GetDefaultInputDevice();
}
UAsyncAction_CommonUserInitialize* Action = NewObject<UAsyncAction_CommonUserInitialize>();
Action->RegisterWithGameInstance(Target);
if (Target && Action->IsRegistered())
{
Action->Subsystem = Target;
Action->Params.RequestedPrivilege = ECommonUserPrivilege::CanPlay;
Action->Params.LocalPlayerIndex = LocalPlayerIndex;
Action->Params.PrimaryInputDevice = PrimaryInputDevice;
Action->Params.bCanUseGuestLogin = bCanUseGuestLogin;
Action->Params.bCanCreateNewLocalPlayer = true;
}
else
{
Action->SetReadyToDestroy();
}
return Action;
}
UAsyncAction_CommonUserInitialize* UAsyncAction_CommonUserInitialize::LoginForOnlinePlay(UCommonUserSubsystem* Target, int32 LocalPlayerIndex)
{
UAsyncAction_CommonUserInitialize* Action = NewObject<UAsyncAction_CommonUserInitialize>();
Action->RegisterWithGameInstance(Target);
if (Target && Action->IsRegistered())
{
Action->Subsystem = Target;
Action->Params.RequestedPrivilege = ECommonUserPrivilege::CanPlayOnline;
Action->Params.LocalPlayerIndex = LocalPlayerIndex;
Action->Params.bCanCreateNewLocalPlayer = false;
}
else
{
Action->SetReadyToDestroy();
}
return Action;
}
void UAsyncAction_CommonUserInitialize::HandleFailure()
{
const UCommonUserInfo* UserInfo = nullptr;
if (Subsystem.IsValid())
{
UserInfo = Subsystem->GetUserInfoForLocalPlayerIndex(Params.LocalPlayerIndex);
}
HandleInitializationComplete(UserInfo, false, NSLOCTEXT("CommonUser", "LoginFailedEarly", "Unable to start login process"), Params.RequestedPrivilege, Params.OnlineContext);
}
void UAsyncAction_CommonUserInitialize::HandleInitializationComplete(const UCommonUserInfo* UserInfo, bool bSuccess, FText Error, ECommonUserPrivilege RequestedPrivilege, ECommonUserOnlineContext OnlineContext)
{
if (ShouldBroadcastDelegates())
{
OnInitializationComplete.Broadcast(UserInfo, bSuccess, Error, RequestedPrivilege, OnlineContext);
}
SetReadyToDestroy();
}
void UAsyncAction_CommonUserInitialize::Activate()
{
if (Subsystem.IsValid())
{
Params.OnUserInitializeComplete.BindUFunction(this, GET_FUNCTION_NAME_CHECKED(UAsyncAction_CommonUserInitialize, HandleInitializationComplete));
bool bSuccess = Subsystem->TryToInitializeUser(Params);
if (!bSuccess)
{
// Call failure next frame
FTimerManager* TimerManager = GetTimerManager();
if (TimerManager)
{
TimerManager->SetTimerForNextTick(FTimerDelegate::CreateUObject(this, &UAsyncAction_CommonUserInitialize::HandleFailure));
}
}
}
else
{
SetReadyToDestroy();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,120 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CommonUserBasicPresence.h"
#include "CommonSessionSubsystem.h"
#include "Engine/GameInstance.h"
#include "Engine/LocalPlayer.h"
#include "CommonUserTypes.h"
#if COMMONUSER_OSSV1
#include "OnlineSubsystemUtils.h"
#include "Interfaces/OnlinePresenceInterface.h"
#else
#include "Online/OnlineServicesEngineUtils.h"
#include "Online/Presence.h"
#endif
DECLARE_LOG_CATEGORY_EXTERN(LogUserBasicPresence, Log, All);
DEFINE_LOG_CATEGORY(LogUserBasicPresence);
UCommonUserBasicPresence::UCommonUserBasicPresence()
{
}
void UCommonUserBasicPresence::Initialize(FSubsystemCollectionBase& Collection)
{
UCommonSessionSubsystem* CommonSession = Collection.InitializeDependency<UCommonSessionSubsystem>();
if(ensure(CommonSession))
{
CommonSession->OnSessionInformationChangedEvent.AddUObject(this, &UCommonUserBasicPresence::OnNotifySessionInformationChanged);
}
}
void UCommonUserBasicPresence::Deinitialize()
{
}
FString UCommonUserBasicPresence::SessionStateToBackendKey(ECommonSessionInformationState SessionStatus)
{
switch (SessionStatus)
{
case ECommonSessionInformationState::OutOfGame:
return PresenceStatusMainMenu;
break;
case ECommonSessionInformationState::Matchmaking:
return PresenceStatusMatchmaking;
break;
case ECommonSessionInformationState::InGame:
return PresenceStatusInGame;
break;
default:
UE_LOG(LogUserBasicPresence, Error, TEXT("UCommonUserBasicPresence::SessionStateToBackendKey: Found unknown enum value %d"), (uint8)SessionStatus);
return TEXT("Unknown");
break;
}
}
void UCommonUserBasicPresence::OnNotifySessionInformationChanged(ECommonSessionInformationState SessionStatus, const FString& GameMode, const FString& MapName)
{
if (bEnableSessionsBasedPresence && !GetGameInstance()->IsDedicatedServerInstance())
{
// trim the map name since its a URL
FString MapNameTruncated = MapName;
if (!MapNameTruncated.IsEmpty())
{
int LastIndexOfSlash = 0;
MapNameTruncated.FindLastChar('/', LastIndexOfSlash);
MapNameTruncated = MapNameTruncated.RightChop(LastIndexOfSlash + 1);
}
#if COMMONUSER_OSSV1
IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
if(OnlineSub)
{
IOnlinePresencePtr Presence = OnlineSub->GetPresenceInterface();
if(Presence)
{
FOnlineUserPresenceStatus UpdatedPresence;
UpdatedPresence.State = EOnlinePresenceState::Online; // We'll only send the presence update if the user has a valid UniqueNetId, so we can assume they are Online
UpdatedPresence.StatusStr = *SessionStateToBackendKey(SessionStatus);
UpdatedPresence.Properties.Emplace(PresenceKeyGameMode, GameMode);
UpdatedPresence.Properties.Emplace(PresenceKeyMapName, MapNameTruncated);
for (const ULocalPlayer* LocalPlayer : GetGameInstance()->GetLocalPlayers())
{
if (LocalPlayer && LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId() != nullptr)
{
Presence->SetPresence(*LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId(), UpdatedPresence);
}
}
}
}
#else
UE::Online::IOnlineServicesPtr OnlineServices = UE::Online::GetServices(GetWorld());
check(OnlineServices);
UE::Online::IPresencePtr Presence = OnlineServices->GetPresenceInterface();
if(Presence)
{
for (const ULocalPlayer* LocalPlayer : GetGameInstance()->GetLocalPlayers())
{
if (LocalPlayer && LocalPlayer->GetPreferredUniqueNetId().IsV2())
{
UE::Online::FPartialUpdatePresence::Params UpdateParams;
UpdateParams.LocalAccountId = LocalPlayer->GetPreferredUniqueNetId().GetV2();
UpdateParams.Mutations.StatusString.Emplace(*SessionStateToBackendKey(SessionStatus));
UpdateParams.Mutations.UpdatedProperties.Emplace(PresenceKeyGameMode, GameMode);
UpdateParams.Mutations.UpdatedProperties.Emplace(PresenceKeyMapName, MapNameTruncated);
Presence->PartialUpdatePresence(MoveTemp(UpdateParams));
}
}
}
#endif
}
}

View File

@ -0,0 +1,22 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CommonUserModule.h"
#include "Modules/ModuleManager.h"
#define LOCTEXT_NAMESPACE "FCommonUserModule"
void FCommonUserModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FCommonUserModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FCommonUserModule, CommonUser)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CommonUserTypes.h"
#include "OnlineError.h"
void FOnlineResultInformation::FromOnlineError(const FOnlineErrorType& InOnlineError)
{
#if COMMONUSER_OSSV1
bWasSuccessful = InOnlineError.WasSuccessful();
ErrorId = InOnlineError.GetErrorCode();
ErrorText = InOnlineError.GetErrorMessage();
#else
bWasSuccessful = InOnlineError != UE::Online::Errors::Success();
ErrorId = InOnlineError.GetErrorId();
ErrorText = InOnlineError.GetText();
#endif
}

View File

@ -0,0 +1,64 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CommonUserSubsystem.h"
#include "Engine/CancellableAsyncAction.h"
#include "AsyncAction_CommonUserInitialize.generated.h"
enum class ECommonUserOnlineContext : uint8;
enum class ECommonUserPrivilege : uint8;
struct FInputDeviceId;
class FText;
class UObject;
struct FFrame;
/**
* Async action to handle different functions for initializing users
*/
UCLASS()
class COMMONUSER_API UAsyncAction_CommonUserInitialize : public UCancellableAsyncAction
{
GENERATED_BODY()
public:
/**
* Initializes a local player with the common user system, which includes doing platform-specific login and privilege checks.
* When the process has succeeded or failed, it will broadcast the OnInitializationComplete delegate.
*
* @param LocalPlayerIndex Desired index of ULocalPlayer in Game Instance, 0 will be primary player and 1+ for local multiplayer
* @param PrimaryInputDevice Primary input device for the user, if invalid will use the system default
* @param bCanUseGuestLogin If true, this player can be a guest without a real system net id
*/
UFUNCTION(BlueprintCallable, Category = CommonUser, meta = (BlueprintInternalUseOnly = "true"))
static UAsyncAction_CommonUserInitialize* InitializeForLocalPlay(UCommonUserSubsystem* Target, int32 LocalPlayerIndex, FInputDeviceId PrimaryInputDevice, bool bCanUseGuestLogin);
/**
* Attempts to log an existing user into the platform-specific online backend to enable full online play
* When the process has succeeded or failed, it will broadcast the OnInitializationComplete delegate.
*
* @param LocalPlayerIndex Index of existing LocalPlayer in Game Instance
*/
UFUNCTION(BlueprintCallable, Category = CommonUser, meta = (BlueprintInternalUseOnly = "true"))
static UAsyncAction_CommonUserInitialize* LoginForOnlinePlay(UCommonUserSubsystem* Target, int32 LocalPlayerIndex);
/** Call when initialization succeeds or fails */
UPROPERTY(BlueprintAssignable)
FCommonUserOnInitializeCompleteMulticast OnInitializationComplete;
/** Fail and send callbacks if needed */
void HandleFailure();
/** Wrapper delegate, will pass on to OnInitializationComplete if appropriate */
UFUNCTION()
virtual void HandleInitializationComplete(const UCommonUserInfo* UserInfo, bool bSuccess, FText Error, ECommonUserPrivilege RequestedPrivilege, ECommonUserOnlineContext OnlineContext);
protected:
/** Actually start the initialization */
virtual void Activate() override;
TWeakObjectPtr<UCommonUserSubsystem> Subsystem;
FCommonUserInitializeParams Params;
};

View File

@ -0,0 +1,471 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CommonUserTypes.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "UObject/ObjectPtr.h"
#include "UObject/StrongObjectPtr.h"
#include "UObject/PrimaryAssetId.h"
#include "UObject/WeakObjectPtr.h"
#include "PartyBeaconClient.h"
#include "PartyBeaconHost.h"
#include "PartyBeaconState.h"
class APlayerController;
class AOnlineBeaconHost;
class ULocalPlayer;
namespace ETravelFailure { enum Type : int; }
struct FOnlineResultInformation;
#if COMMONUSER_OSSV1
#include "Interfaces/OnlineSessionInterface.h"
#include "OnlineSessionSettings.h"
#else
#include "Online/Lobbies.h"
#include "Online/OnlineAsyncOpHandle.h"
#endif // COMMONUSER_OSSV1
#include "CommonSessionSubsystem.generated.h"
class UWorld;
class FCommonSession_OnlineSessionSettings;
#if COMMONUSER_OSSV1
class FCommonOnlineSearchSettingsOSSv1;
using FCommonOnlineSearchSettings = FCommonOnlineSearchSettingsOSSv1;
#else
class FCommonOnlineSearchSettingsOSSv2;
using FCommonOnlineSearchSettings = FCommonOnlineSearchSettingsOSSv2;
#endif // COMMONUSER_OSSV1
//////////////////////////////////////////////////////////////////////
// UCommonSession_HostSessionRequest
/** Specifies the online features and connectivity that should be used for a game session */
UENUM(BlueprintType)
enum class ECommonSessionOnlineMode : uint8
{
Offline,
LAN,
Online
};
/** A request object that stores the parameters used when hosting a gameplay session */
UCLASS(BlueprintType)
class COMMONUSER_API UCommonSession_HostSessionRequest : public UObject
{
GENERATED_BODY()
public:
/** Indicates if the session is a full online session or a different type */
UPROPERTY(BlueprintReadWrite, Category=Session)
ECommonSessionOnlineMode OnlineMode;
/** True if this request should create a player-hosted lobbies if available */
UPROPERTY(BlueprintReadWrite, Category = Session)
bool bUseLobbies;
/** True if this request should create a lobby with enabled voice chat in available */
UPROPERTY(BlueprintReadWrite, Category = Session)
bool bUseLobbiesVoiceChat;
/** True if this request should create a session that will appear in the user's presence information */
UPROPERTY(BlueprintReadWrite, Category = Session)
bool bUsePresence;
/** String used during matchmaking to specify what type of game mode this is */
UPROPERTY(BlueprintReadWrite, Category=Session)
FString ModeNameForAdvertisement;
/** The map that will be loaded at the start of gameplay, this needs to be a valid Primary Asset top-level map */
UPROPERTY(BlueprintReadWrite, Category=Session, meta=(AllowedTypes="World"))
FPrimaryAssetId MapID;
/** Extra arguments passed as URL options to the game */
UPROPERTY(BlueprintReadWrite, Category=Session)
TMap<FString, FString> ExtraArgs;
/** Maximum players allowed per gameplay session */
UPROPERTY(BlueprintReadWrite, Category=Session)
int32 MaxPlayerCount = 16;
public:
/** Returns the maximum players that should actually be used, could be overridden in child classes */
virtual int32 GetMaxPlayers() const;
/** Returns the full map name that will be used during gameplay */
virtual FString GetMapName() const;
/** Constructs the full URL that will be passed to ServerTravel */
virtual FString ConstructTravelURL() const;
/** Returns true if this request is valid, returns false and logs errors if it is not */
virtual bool ValidateAndLogErrors(FText& OutError) const;
};
//////////////////////////////////////////////////////////////////////
// UCommonSession_SearchResult
/** A result object returned from the online system that describes a joinable game session */
UCLASS(BlueprintType)
class COMMONUSER_API UCommonSession_SearchResult : public UObject
{
GENERATED_BODY()
public:
/** Returns an internal description of the session, not meant to be human readable */
UFUNCTION(BlueprintCallable, Category=Session)
FString GetDescription() const;
/** Gets an arbitrary string setting, bFoundValue will be false if the setting does not exist */
UFUNCTION(BlueprintPure, Category=Sessions)
void GetStringSetting(FName Key, FString& Value, bool& bFoundValue) const;
/** Gets an arbitrary integer setting, bFoundValue will be false if the setting does not exist */
UFUNCTION(BlueprintPure, Category = Sessions)
void GetIntSetting(FName Key, int32& Value, bool& bFoundValue) const;
/** The number of private connections that are available */
UFUNCTION(BlueprintPure, Category=Sessions)
int32 GetNumOpenPrivateConnections() const;
/** The number of publicly available connections that are available */
UFUNCTION(BlueprintPure, Category=Sessions)
int32 GetNumOpenPublicConnections() const;
/** The maximum number of publicly available connections that could be available, including already filled connections */
UFUNCTION(BlueprintPure, Category = Sessions)
int32 GetMaxPublicConnections() const;
/** Ping to the search result, MAX_QUERY_PING is unreachable */
UFUNCTION(BlueprintPure, Category=Sessions)
int32 GetPingInMs() const;
public:
/** Pointer to the platform-specific implementation */
#if COMMONUSER_OSSV1
FOnlineSessionSearchResult Result;
#else
TSharedPtr<const UE::Online::FLobby> Lobby;
#endif // COMMONUSER_OSSV1
};
//////////////////////////////////////////////////////////////////////
// UCommonSession_SearchSessionRequest
/** Delegates called when a session search completes */
DECLARE_MULTICAST_DELEGATE_TwoParams(FCommonSession_FindSessionsFinished, bool bSucceeded, const FText& ErrorMessage);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FCommonSession_FindSessionsFinishedDynamic, bool, bSucceeded, FText, ErrorMessage);
/** Request object describing a session search, this object will be updated once the search has completed */
UCLASS(BlueprintType)
class COMMONUSER_API UCommonSession_SearchSessionRequest : public UObject
{
GENERATED_BODY()
public:
/** Indicates if the this is looking for full online games or a different type like LAN */
UPROPERTY(BlueprintReadWrite, Category = Session)
ECommonSessionOnlineMode OnlineMode;
/** True if this request should look for player-hosted lobbies if they are available, false will only search for registered server sessions */
UPROPERTY(BlueprintReadWrite, Category = Session)
bool bUseLobbies;
/** List of all found sessions, will be valid when OnSearchFinished is called */
UPROPERTY(BlueprintReadOnly, Category=Session)
TArray<TObjectPtr<UCommonSession_SearchResult>> Results;
/** Native Delegate called when a session search completes */
FCommonSession_FindSessionsFinished OnSearchFinished;
/** Called by subsystem to execute finished delegates */
void NotifySearchFinished(bool bSucceeded, const FText& ErrorMessage);
private:
/** Delegate called when a session search completes */
UPROPERTY(BlueprintAssignable, Category = "Events", meta = (DisplayName = "On Search Finished", AllowPrivateAccess = true))
FCommonSession_FindSessionsFinishedDynamic K2_OnSearchFinished;
};
//////////////////////////////////////////////////////////////////////
// CommonSessionSubsystem Events
/**
* Event triggered when the local user has requested to join a session from an external source, for example from a platform overlay.
* Generally, the game should transition the player into the session.
* @param LocalPlatformUserId the local user id that accepted the invitation. This is a platform user id because the user might not be signed in yet.
* @param RequestedSession the requested session. Can be null if there was an error processing the request.
* @param RequestedSessionResult result of the requested session processing
*/
DECLARE_MULTICAST_DELEGATE_ThreeParams(FCommonSessionOnUserRequestedSession, const FPlatformUserId& /*LocalPlatformUserId*/, UCommonSession_SearchResult* /*RequestedSession*/, const FOnlineResultInformation& /*RequestedSessionResult*/);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FCommonSessionOnUserRequestedSession_Dynamic, const FPlatformUserId&, LocalPlatformUserId, UCommonSession_SearchResult*, RequestedSession, const FOnlineResultInformation&, RequestedSessionResult);
/**
* Event triggered when a session join has completed, after joining the underlying session and before traveling to the server if it was successful.
* The event parameters indicate if this was successful, or if there was an error that will stop it from traveling.
* @param Result result of the session join
*/
DECLARE_MULTICAST_DELEGATE_OneParam(FCommonSessionOnJoinSessionComplete, const FOnlineResultInformation& /*Result*/);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FCommonSessionOnJoinSessionComplete_Dynamic, const FOnlineResultInformation&, Result);
/**
* Event triggered when a session creation for hosting has completed, right before it travels to the map.
* The event parameters indicate if this was successful, or if there was an error that will stop it from traveling.
* @param Result result of the session join
*/
DECLARE_MULTICAST_DELEGATE_OneParam(FCommonSessionOnCreateSessionComplete, const FOnlineResultInformation& /*Result*/);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FCommonSessionOnCreateSessionComplete_Dynamic, const FOnlineResultInformation&, Result);
/**
* Event triggered when the local user has requested to destroy a session from an external source, for example from a platform overlay.
* The game should transition the player out of the session.
* @param LocalPlatformUserId the local user id that made the destroy request. This is a platform user id because the user might not be signed in yet.
* @param SessionName the name identifier for the session.
*/
DECLARE_MULTICAST_DELEGATE_TwoParams(FCommonSessionOnDestroySessionRequested, const FPlatformUserId& /*LocalPlatformUserId*/, const FName& /*SessionName*/);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FCommonSessionOnDestroySessionRequested_Dynamic, const FPlatformUserId&, LocalPlatformUserId, const FName&, SessionName);
/**
* Event triggered when a session join has completed, after resolving the connect string and prior to the client traveling.
* @param URL resolved connection string for the session with any additional arguments
*/
DECLARE_MULTICAST_DELEGATE_OneParam(FCommonSessionOnPreClientTravel, FString& /*URL*/);
/**
* Event triggered at different points in the session ecosystem that represent a user-presentable state of the session.
* This should not be used for online functionality (use OnCreateSessionComplete or OnJoinSessionComplete for those) but for features such as rich presence
*/
UENUM(BlueprintType)
enum class ECommonSessionInformationState : uint8
{
OutOfGame,
Matchmaking,
InGame
};
DECLARE_MULTICAST_DELEGATE_ThreeParams(FCommonSessionOnSessionInformationChanged, ECommonSessionInformationState /*SessionStatus*/, const FString& /*GameMode*/, const FString& /*MapName*/);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FCommonSessionOnSessionInformationChanged_Dynamic, ECommonSessionInformationState, SessionStatus, const FString&, GameMode, const FString&, MapName);
//////////////////////////////////////////////////////////////////////
// UCommonSessionSubsystem
/**
* Game subsystem that handles requests for hosting and joining online games.
* One subsystem is created for each game instance and can be accessed from blueprints or C++ code.
* If a game-specific subclass exists, this base subsystem will not be created.
*/
UCLASS(BlueprintType, Config=Engine)
class COMMONUSER_API UCommonSessionSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
UCommonSessionSubsystem() { }
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
/** Creates a host session request with default options for online games, this can be modified after creation */
UFUNCTION(BlueprintCallable, Category = Session)
virtual UCommonSession_HostSessionRequest* CreateOnlineHostSessionRequest();
/** Creates a session search object with default options to look for default online games, this can be modified after creation */
UFUNCTION(BlueprintCallable, Category = Session)
virtual UCommonSession_SearchSessionRequest* CreateOnlineSearchSessionRequest();
/** Creates a new online game using the session request information, if successful this will start a hard map transfer */
UFUNCTION(BlueprintCallable, Category=Session)
virtual void HostSession(APlayerController* HostingPlayer, UCommonSession_HostSessionRequest* Request);
/** Starts a process to look for existing sessions or create a new one if no viable sessions are found */
UFUNCTION(BlueprintCallable, Category=Session)
virtual void QuickPlaySession(APlayerController* JoiningOrHostingPlayer, UCommonSession_HostSessionRequest* Request);
/** Starts process to join an existing session, if successful this will connect to the specified server */
UFUNCTION(BlueprintCallable, Category=Session)
virtual void JoinSession(APlayerController* JoiningPlayer, UCommonSession_SearchResult* Request);
/** Queries online system for the list of joinable sessions matching the search request */
UFUNCTION(BlueprintCallable, Category=Session)
virtual void FindSessions(APlayerController* SearchingPlayer, UCommonSession_SearchSessionRequest* Request);
/** Clean up any active sessions, called from cases like returning to the main menu */
UFUNCTION(BlueprintCallable, Category=Session)
virtual void CleanUpSessions();
//////////////////////////////////////////////////////////////////////
// Events
/** Native Delegate when a local user has accepted an invite */
FCommonSessionOnUserRequestedSession OnUserRequestedSessionEvent;
/** Event broadcast when a local user has accepted an invite */
UPROPERTY(BlueprintAssignable, Category = "Events", meta = (DisplayName = "On User Requested Session"))
FCommonSessionOnUserRequestedSession_Dynamic K2_OnUserRequestedSessionEvent;
/** Native Delegate when a JoinSession call has completed */
FCommonSessionOnJoinSessionComplete OnJoinSessionCompleteEvent;
/** Event broadcast when a JoinSession call has completed */
UPROPERTY(BlueprintAssignable, Category = "Events", meta = (DisplayName = "On Join Session Complete"))
FCommonSessionOnJoinSessionComplete_Dynamic K2_OnJoinSessionCompleteEvent;
/** Native Delegate when a CreateSession call has completed */
FCommonSessionOnCreateSessionComplete OnCreateSessionCompleteEvent;
/** Event broadcast when a CreateSession call has completed */
UPROPERTY(BlueprintAssignable, Category = "Events", meta = (DisplayName = "On Create Session Complete"))
FCommonSessionOnCreateSessionComplete_Dynamic K2_OnCreateSessionCompleteEvent;
/** Native Delegate when the presentable session information has changed */
FCommonSessionOnSessionInformationChanged OnSessionInformationChangedEvent;
/** Event broadcast when the presentable session information has changed */
UPROPERTY(BlueprintAssignable, Category = "Events", meta = (DisplayName = "On Session Information Changed"))
FCommonSessionOnSessionInformationChanged_Dynamic K2_OnSessionInformationChangedEvent;
/** Native Delegate when a platform session destroy has been requested */
FCommonSessionOnDestroySessionRequested OnDestroySessionRequestedEvent;
/** Event broadcast when a platform session destroy has been requested */
UPROPERTY(BlueprintAssignable, Category = "Events", meta = (DisplayName = "On Leave Session Requested"))
FCommonSessionOnDestroySessionRequested_Dynamic K2_OnDestroySessionRequestedEvent;
/** Native Delegate for modifying the connect URL prior to a client travel */
FCommonSessionOnPreClientTravel OnPreClientTravelEvent;
// Config settings, these can overridden in child classes or config files
/** Sets the default value of bUseLobbies for session search and host requests */
UPROPERTY(Config)
bool bUseLobbiesDefault = true;
/** Sets the default value of bUseLobbiesVoiceChat for session host requests */
UPROPERTY(Config)
bool bUseLobbiesVoiceChatDefault = false;
/** Enables reservation beacon flow prior to server travel when creating or joining a game session */
UPROPERTY(Config)
bool bUseBeacons = true;
protected:
// Functions called during the process of creating or joining a session, these can be overidden for game-specific behavior
/** Called to fill in a session request from quick play host settings, can be overridden for game-specific behavior */
virtual TSharedRef<FCommonOnlineSearchSettings> CreateQuickPlaySearchSettings(UCommonSession_HostSessionRequest* Request, UCommonSession_SearchSessionRequest* QuickPlayRequest);
/** Called when a quick play search finishes, can be overridden for game-specific behavior */
virtual void HandleQuickPlaySearchFinished(bool bSucceeded, const FText& ErrorMessage, TWeakObjectPtr<APlayerController> JoiningOrHostingPlayer, TStrongObjectPtr<UCommonSession_HostSessionRequest> HostRequest);
/** Called when traveling to a session fails */
virtual void TravelLocalSessionFailure(UWorld* World, ETravelFailure::Type FailureType, const FString& ReasonString);
/** Called when a new session is either created or fails to be created */
virtual void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful);
/** Called to finalize session creation */
virtual void FinishSessionCreation(bool bWasSuccessful);
/** Called after traveling to the new hosted session map */
virtual void HandlePostLoadMap(UWorld* World);
protected:
// Internal functions for initializing and handling results from the online systems
void BindOnlineDelegates();
void CreateOnlineSessionInternal(ULocalPlayer* LocalPlayer, UCommonSession_HostSessionRequest* Request);
void FindSessionsInternal(APlayerController* SearchingPlayer, const TSharedRef<FCommonOnlineSearchSettings>& InSearchSettings);
void JoinSessionInternal(ULocalPlayer* LocalPlayer, UCommonSession_SearchResult* Request);
void InternalTravelToSession(const FName SessionName);
void NotifyUserRequestedSession(const FPlatformUserId& PlatformUserId, UCommonSession_SearchResult* RequestedSession, const FOnlineResultInformation& RequestedSessionResult);
void NotifyJoinSessionComplete(const FOnlineResultInformation& Result);
void NotifyCreateSessionComplete(const FOnlineResultInformation& Result);
void NotifySessionInformationUpdated(ECommonSessionInformationState SessionStatusStr, const FString& GameMode = FString(), const FString& MapName = FString());
void NotifyDestroySessionRequested(const FPlatformUserId& PlatformUserId, const FName& SessionName);
void SetCreateSessionError(const FText& ErrorText);
#if COMMONUSER_OSSV1
void BindOnlineDelegatesOSSv1();
void CreateOnlineSessionInternalOSSv1(ULocalPlayer* LocalPlayer, UCommonSession_HostSessionRequest* Request);
void FindSessionsInternalOSSv1(ULocalPlayer* LocalPlayer);
void JoinSessionInternalOSSv1(ULocalPlayer* LocalPlayer, UCommonSession_SearchResult* Request);
TSharedRef<FCommonOnlineSearchSettings> CreateQuickPlaySearchSettingsOSSv1(UCommonSession_HostSessionRequest* Request, UCommonSession_SearchSessionRequest* QuickPlayRequest);
void CleanUpSessionsOSSv1();
void HandleSessionFailure(const FUniqueNetId& NetId, ESessionFailure::Type FailureType);
void HandleSessionUserInviteAccepted(const bool bWasSuccessful, const int32 LocalUserIndex, FUniqueNetIdPtr AcceptingUserId, const FOnlineSessionSearchResult& SearchResult);
void OnStartSessionComplete(FName SessionName, bool bWasSuccessful);
void OnRegisterLocalPlayerComplete_CreateSession(const FUniqueNetId& PlayerId, EOnJoinSessionCompleteResult::Type Result);
void OnUpdateSessionComplete(FName SessionName, bool bWasSuccessful);
void OnEndSessionComplete(FName SessionName, bool bWasSuccessful);
void OnDestroySessionComplete(FName SessionName, bool bWasSuccessful);
void OnDestroySessionRequested(int32 LocalUserNum, FName SessionName);
void OnFindSessionsComplete(bool bWasSuccessful);
void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);
void OnRegisterJoiningLocalPlayerComplete(const FUniqueNetId& PlayerId, EOnJoinSessionCompleteResult::Type Result);
void FinishJoinSession(EOnJoinSessionCompleteResult::Type Result);
#else
void BindOnlineDelegatesOSSv2();
void CreateOnlineSessionInternalOSSv2(ULocalPlayer* LocalPlayer, UCommonSession_HostSessionRequest* Request);
void FindSessionsInternalOSSv2(ULocalPlayer* LocalPlayer);
void JoinSessionInternalOSSv2(ULocalPlayer* LocalPlayer, UCommonSession_SearchResult* Request);
TSharedRef<FCommonOnlineSearchSettings> CreateQuickPlaySearchSettingsOSSv2(UCommonSession_HostSessionRequest* HostRequest, UCommonSession_SearchSessionRequest* SearchRequest);
void CleanUpSessionsOSSv2();
/** Process a join request originating from the online service */
void OnSessionJoinRequested(const UE::Online::FUILobbyJoinRequested& EventParams);
/** Get the local user id for a given controller */
UE::Online::FAccountId GetAccountId(APlayerController* PlayerController) const;
/** Get the lobby id for a given session name */
UE::Online::FLobbyId GetLobbyId(const FName SessionName) const;
/** Event handle for UI lobby join requested */
UE::Online::FOnlineEventDelegateHandle LobbyJoinRequestedHandle;
#endif // COMMONUSER_OSSV1
void CreateHostReservationBeacon();
void ConnectToHostReservationBeacon();
void DestroyHostReservationBeacon();
protected:
/** The travel URL that will be used after session operations are complete */
FString PendingTravelURL;
/** Most recent result information for a session creation attempt, stored here to allow storing error codes for later */
FOnlineResultInformation CreateSessionResult;
/** True if we want to cancel the session after it is created */
bool bWantToDestroyPendingSession = false;
/** True if this is a dedicated server, which doesn't require a LocalPlayer to create a session */
bool bIsDedicatedServer = false;
/** Settings for the current search */
TSharedPtr<FCommonOnlineSearchSettings> SearchSettings;
/** General beacon listener for registering beacons with */
UPROPERTY(Transient)
TWeakObjectPtr<AOnlineBeaconHost> BeaconHostListener;
/** State of the beacon host */
UPROPERTY(Transient)
TObjectPtr<UPartyBeaconState> ReservationBeaconHostState;
/** Beacon controlling access to this game. */
UPROPERTY(Transient)
TWeakObjectPtr<APartyBeaconHost> ReservationBeaconHost;
/** Common class object for beacon communication */
UPROPERTY(Transient)
TWeakObjectPtr<APartyBeaconClient> ReservationBeaconClient;
/** Number of teams for beacon reservation */
UPROPERTY(Config)
int32 BeaconTeamCount = 2;
/** Size of a team for beacon reservation */
UPROPERTY(Config)
int32 BeaconTeamSize = 8;
/** Max number of beacon reservations */
UPROPERTY(Config)
int32 BeaconMaxReservations = 16;
};

View File

@ -0,0 +1,59 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Subsystems/GameInstanceSubsystem.h"
#include "CommonUserBasicPresence.generated.h"
class UCommonSessionSubsystem;
enum class ECommonSessionInformationState : uint8;
//////////////////////////////////////////////////////////////////////
// UCommonUserBasicPresence
/**
* This subsystem plugs into the session subsystem and pushes its information to the presence interface.
* It is not intended to be a full featured rich presence implementation, but can be used as a proof-of-concept
* for pushing information from the session subsystem to the presence system
*/
UCLASS(BlueprintType, Config = Engine)
class COMMONUSER_API UCommonUserBasicPresence : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
UCommonUserBasicPresence();
/** Implement this for initialization of instances of the system */
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
/** Implement this for deinitialization of instances of the system */
virtual void Deinitialize() override;
/** False is a general purpose killswitch to stop this class from pushing presence*/
UPROPERTY(Config)
bool bEnableSessionsBasedPresence = false;
/** Maps the presence status "In-game" to a backend key*/
UPROPERTY(Config)
FString PresenceStatusInGame;
/** Maps the presence status "Main Menu" to a backend key*/
UPROPERTY(Config)
FString PresenceStatusMainMenu;
/** Maps the presence status "Matchmaking" to a backend key*/
UPROPERTY(Config)
FString PresenceStatusMatchmaking;
/** Maps the "Game Mode" rich presence entry to a backend key*/
UPROPERTY(Config)
FString PresenceKeyGameMode;
/** Maps the "Map Name" rich presence entry to a backend key*/
UPROPERTY(Config)
FString PresenceKeyMapName;
void OnNotifySessionInformationChanged(ECommonSessionInformationState SessionStatus, const FString& GameMode, const FString& MapName);
FString SessionStateToBackendKey(ECommonSessionInformationState SessionStatus);
};

View File

@ -0,0 +1,14 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Modules/ModuleInterface.h"
class FCommonUserModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View File

@ -0,0 +1,649 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CommonUserTypes.h"
#include "Engine/GameViewportClient.h"
#include "GameFramework/OnlineReplStructs.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "UObject/WeakObjectPtr.h"
#include "GameplayTagContainer.h"
#include "CommonUserSubsystem.generated.h"
#if COMMONUSER_OSSV1
#include "Interfaces/OnlineIdentityInterface.h"
#include "OnlineError.h"
#else
#include "Online/OnlineAsyncOpHandle.h"
#endif
class FNativeGameplayTag;
class IOnlineSubsystem;
/** List of tags used by the common user subsystem */
struct COMMONUSER_API FCommonUserTags
{
// General severity levels and specific system messages
static FNativeGameplayTag SystemMessage_Error; // SystemMessage.Error
static FNativeGameplayTag SystemMessage_Warning; // SystemMessage.Warning
static FNativeGameplayTag SystemMessage_Display; // SystemMessage.Display
/** All attempts to initialize a player failed, user has to do something before trying again */
static FNativeGameplayTag SystemMessage_Error_InitializeLocalPlayerFailed; // SystemMessage.Error.InitializeLocalPlayerFailed
// Platform trait tags, it is expected that the game instance or other system calls SetTraitTags with these tags for the appropriate platform
/** This tag means it is a console platform that directly maps controller IDs to different system users. If false, the same user can have multiple controllers */
static FNativeGameplayTag Platform_Trait_RequiresStrictControllerMapping; // Platform.Trait.RequiresStrictControllerMapping
/** This tag means the platform has a single online user and all players use index 0 */
static FNativeGameplayTag Platform_Trait_SingleOnlineUser; // Platform.Trait.SingleOnlineUser
};
/** Logical representation of an individual user, one of these will exist for all initialized local players */
UCLASS(BlueprintType)
class COMMONUSER_API UCommonUserInfo : public UObject
{
GENERATED_BODY()
public:
/** Primary controller input device for this user, they could also have additional secondary devices */
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
FInputDeviceId PrimaryInputDevice;
/** Specifies the logical user on the local platform, guest users will point to the primary user */
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
FPlatformUserId PlatformUser;
/** If this user is assigned a LocalPlayer, this will match the index in the GameInstance localplayers array once it is fully created */
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
int32 LocalPlayerIndex = -1;
/** If true, this user is allowed to be a guest */
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
bool bCanBeGuest = false;
/** If true, this is a guest user attached to primary user 0 */
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
bool bIsGuest = false;
/** Overall state of the user's initialization process */
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
ECommonUserInitializationState InitializationState = ECommonUserInitializationState::Invalid;
/** Returns true if this user has successfully logged in */
UFUNCTION(BlueprintCallable, Category = UserInfo)
bool IsLoggedIn() const;
/** Returns true if this user is in the middle of logging in */
UFUNCTION(BlueprintCallable, Category = UserInfo)
bool IsDoingLogin() const;
/** Returns the most recently queries result for a specific privilege, will return unknown if never queried */
UFUNCTION(BlueprintCallable, Category = UserInfo)
ECommonUserPrivilegeResult GetCachedPrivilegeResult(ECommonUserPrivilege Privilege, ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
/** Ask about the general availability of a feature, this combines cached results with state */
UFUNCTION(BlueprintCallable, Category = UserInfo)
ECommonUserAvailability GetPrivilegeAvailability(ECommonUserPrivilege Privilege) const;
/** Returns the net id for the given context */
UFUNCTION(BlueprintCallable, Category = UserInfo)
FUniqueNetIdRepl GetNetId(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
/** Returns the user's human readable nickname, this will return the value that was cached during UpdateCachedNetId or SetNickname */
UFUNCTION(BlueprintCallable, Category = UserInfo)
FString GetNickname(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
/** Modify the user's human readable nickname, this can be used when setting up multiple guests but will get overwritten with the platform nickname for real users */
UFUNCTION(BlueprintCallable, Category = UserInfo)
void SetNickname(const FString& NewNickname, ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game);
/** Returns an internal debug string for this player */
UFUNCTION(BlueprintCallable, Category = UserInfo)
FString GetDebugString() const;
/** Accessor for platform user id */
FPlatformUserId GetPlatformUserId() const;
/** Gets the platform user index for older functions expecting an integer */
int32 GetPlatformUserIndex() const;
// Internal data, only intended to be accessed by online subsystems
/** Cached data for each online system */
struct FCachedData
{
/** Cached net id per system */
FUniqueNetIdRepl CachedNetId;
/** Cached nickanem, updated whenever net ID might change */
FString CachedNickname;
/** Cached values of various user privileges */
TMap<ECommonUserPrivilege, ECommonUserPrivilegeResult> CachedPrivileges;
};
/** Per context cache, game will always exist but others may not */
TMap<ECommonUserOnlineContext, FCachedData> CachedDataMap;
/** Looks up cached data using resolution rules */
FCachedData* GetCachedData(ECommonUserOnlineContext Context);
const FCachedData* GetCachedData(ECommonUserOnlineContext Context) const;
/** Updates cached privilege results, will propagate to game if needed */
void UpdateCachedPrivilegeResult(ECommonUserPrivilege Privilege, ECommonUserPrivilegeResult Result, ECommonUserOnlineContext Context);
/** Updates cached privilege results, will propagate to game if needed */
void UpdateCachedNetId(const FUniqueNetIdRepl& NewId, ECommonUserOnlineContext Context);
/** Return the subsystem this is owned by */
class UCommonUserSubsystem* GetSubsystem() const;
};
/** Delegates when initialization processes succeed or fail */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FCommonUserOnInitializeCompleteMulticast, const UCommonUserInfo*, UserInfo, bool, bSuccess, FText, Error, ECommonUserPrivilege, RequestedPrivilege, ECommonUserOnlineContext, OnlineContext);
DECLARE_DYNAMIC_DELEGATE_FiveParams(FCommonUserOnInitializeComplete, const UCommonUserInfo*, UserInfo, bool, bSuccess, FText, Error, ECommonUserPrivilege, RequestedPrivilege, ECommonUserOnlineContext, OnlineContext);
/** Delegate when a system error message is sent, the game can choose to display it to the user using the type tag */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FCommonUserHandleSystemMessageDelegate, FGameplayTag, MessageType, FText, TitleText, FText, BodyText);
/** Delegate when a privilege changes, this can be bound to see if online status/etc changes during gameplay */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FCommonUserAvailabilityChangedDelegate, const UCommonUserInfo*, UserInfo, ECommonUserPrivilege, Privilege, ECommonUserAvailability, OldAvailability, ECommonUserAvailability, NewAvailability);
/** Parameter struct for initialize functions, this would normally be filled in by wrapper functions like async nodes */
USTRUCT(BlueprintType)
struct COMMONUSER_API FCommonUserInitializeParams
{
GENERATED_BODY()
/** What local player index to use, can specify one above current if can create player is enabled */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
int32 LocalPlayerIndex = 0;
/** Deprecated method of selecting platform user and input device */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
int32 ControllerId = -1;
/** Primary controller input device for this user, they could also have additional secondary devices */
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
FInputDeviceId PrimaryInputDevice;
/** Specifies the logical user on the local platform */
UPROPERTY(BlueprintReadOnly, Category = UserInfo)
FPlatformUserId PlatformUser;
/** Generally either CanPlay or CanPlayOnline, specifies what level of privilege is required */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
ECommonUserPrivilege RequestedPrivilege = ECommonUserPrivilege::CanPlay;
/** What specific online context to log in to, game means to login to all relevant ones */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
ECommonUserOnlineContext OnlineContext = ECommonUserOnlineContext::Game;
/** True if this is allowed to create a new local player for initial login */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
bool bCanCreateNewLocalPlayer = false;
/** True if this player can be a guest user without an actual online presence */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
bool bCanUseGuestLogin = false;
/** True if we should not show login errors, the game will be responsible for displaying them */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
bool bSuppressLoginErrors = false;
/** If bound, call this dynamic delegate at completion of login */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Default)
FCommonUserOnInitializeComplete OnUserInitializeComplete;
};
/**
* Game subsystem that handles queries and changes to user identity and login status.
* One subsystem is created for each game instance and can be accessed from blueprints or C++ code.
* If a game-specific subclass exists, this base subsystem will not be created.
*/
UCLASS(BlueprintType, Config=Engine)
class COMMONUSER_API UCommonUserSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
UCommonUserSubsystem() { }
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
/** BP delegate called when any requested initialization request completes */
UPROPERTY(BlueprintAssignable, Category = CommonUser)
FCommonUserOnInitializeCompleteMulticast OnUserInitializeComplete;
/** BP delegate called when the system sends an error/warning message */
UPROPERTY(BlueprintAssignable, Category = CommonUser)
FCommonUserHandleSystemMessageDelegate OnHandleSystemMessage;
/** BP delegate called when privilege availability changes for a user */
UPROPERTY(BlueprintAssignable, Category = CommonUser)
FCommonUserAvailabilityChangedDelegate OnUserPrivilegeChanged;
/** Send a system message via OnHandleSystemMessage */
UFUNCTION(BlueprintCallable, Category = CommonUser)
virtual void SendSystemMessage(FGameplayTag MessageType, FText TitleText, FText BodyText);
/** Sets the maximum number of local players, will not destroy existing ones */
UFUNCTION(BlueprintCallable, Category = CommonUser)
virtual void SetMaxLocalPlayers(int32 InMaxLocalPLayers);
/** Gets the maximum number of local players */
UFUNCTION(BlueprintPure, Category = CommonUser)
int32 GetMaxLocalPlayers() const;
/** Gets the current number of local players, will always be at least 1 */
UFUNCTION(BlueprintPure, Category = CommonUser)
int32 GetNumLocalPlayers() const;
/** Returns the state of initializing the specified local player */
UFUNCTION(BlueprintPure, Category = CommonUser)
ECommonUserInitializationState GetLocalPlayerInitializationState(int32 LocalPlayerIndex) const;
/** Returns the user info for a given local player index in game instance, 0 is always valid in a running game */
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = CommonUser)
const UCommonUserInfo* GetUserInfoForLocalPlayerIndex(int32 LocalPlayerIndex) const;
/** Deprecated, use PlatformUserId when available */
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = CommonUser)
const UCommonUserInfo* GetUserInfoForPlatformUserIndex(int32 PlatformUserIndex) const;
/** Returns the primary user info for a given platform user index. Can return null */
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = CommonUser)
const UCommonUserInfo* GetUserInfoForPlatformUser(FPlatformUserId PlatformUser) const;
/** Returns the user info for a unique net id. Can return null */
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = CommonUser)
const UCommonUserInfo* GetUserInfoForUniqueNetId(const FUniqueNetIdRepl& NetId) const;
/** Deprecated, use InputDeviceId when available */
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = CommonUser)
const UCommonUserInfo* GetUserInfoForControllerId(int32 ControllerId) const;
/** Returns the user info for a given input device. Can return null */
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = CommonUser)
const UCommonUserInfo* GetUserInfoForInputDevice(FInputDeviceId InputDevice) const;
/**
* Tries to start the process of creating or updating a local player, including logging in and creating a player controller.
* When the process has succeeded or failed, it will broadcast the OnUserInitializeComplete delegate.
*
* @param LocalPlayerIndex Desired index of LocalPlayer in Game Instance, 0 will be primary player and 1+ for local multiplayer
* @param PrimaryInputDevice The physical controller that should be mapped to this user, will use the default device if invalid
* @param bCanUseGuestLogin If true, this player can be a guest without a real Unique Net Id
*
* @returns true if the process was started, false if it failed before properly starting
*/
UFUNCTION(BlueprintCallable, Category = CommonUser)
virtual bool TryToInitializeForLocalPlay(int32 LocalPlayerIndex, FInputDeviceId PrimaryInputDevice, bool bCanUseGuestLogin);
/**
* Starts the process of taking a locally logged in user and doing a full online login including account permission checks.
* When the process has succeeded or failed, it will broadcast the OnUserInitializeComplete delegate.
*
* @param LocalPlayerIndex Index of existing LocalPlayer in Game Instance
*
* @returns true if the process was started, false if it failed before properly starting
*/
UFUNCTION(BlueprintCallable, Category = CommonUser)
virtual bool TryToLoginForOnlinePlay(int32 LocalPlayerIndex);
/**
* Starts a general user login and initialization process, using the params structure to determine what to log in to.
* When the process has succeeded or failed, it will broadcast the OnUserInitializeComplete delegate.
* AsyncAction_CommonUserInitialize provides several wrapper functions for using this in an Event graph.
*
* @returns true if the process was started, false if it failed before properly starting
*/
UFUNCTION(BlueprintCallable, Category = CommonUser)
virtual bool TryToInitializeUser(FCommonUserInitializeParams Params);
/**
* Starts the process of listening for user input for new and existing controllers and logging them.
* This will insert a key input handler on the active GameViewportClient and is turned off by calling again with empty key arrays.
*
* @param AnyUserKeys Listen for these keys for any user, even the default user. Set this for an initial press start screen or empty to disable
* @param NewUserKeys Listen for these keys for a new user without a player controller. Set this for splitscreen/local multiplayer or empty to disable
* @param Params Params passed to TryToInitializeUser after detecting key input
*/
UFUNCTION(BlueprintCallable, Category = CommonUser)
virtual void ListenForLoginKeyInput(TArray<FKey> AnyUserKeys, TArray<FKey> NewUserKeys, FCommonUserInitializeParams Params);
/** Attempts to cancel an in-progress initialization attempt, this may not work on all platforms but will disable callbacks */
UFUNCTION(BlueprintCallable, Category = CommonUser)
virtual bool CancelUserInitialization(int32 LocalPlayerIndex);
/** Logs a player out of any online systems, and optionally destroys the player entirely if it's not the first one */
UFUNCTION(BlueprintCallable, Category = CommonUser)
virtual bool TryToLogOutUser(int32 LocalPlayerIndex, bool bDestroyPlayer = false);
/** Resets the login and initialization state when returning to the main menu after an error */
UFUNCTION(BlueprintCallable, Category = CommonUser)
virtual void ResetUserState();
/** Returns true if this this could be a real platform user with a valid identity (even if not currently logged in) */
virtual bool IsRealPlatformUserIndex(int32 PlatformUserIndex) const;
/** Returns true if this this could be a real platform user with a valid identity (even if not currently logged in) */
virtual bool IsRealPlatformUser(FPlatformUserId PlatformUser) const;
/** Converts index to id */
virtual FPlatformUserId GetPlatformUserIdForIndex(int32 PlatformUserIndex) const;
/** Converts id to index */
virtual int32 GetPlatformUserIndexForId(FPlatformUserId PlatformUser) const;
/** Gets the user for an input device */
virtual FPlatformUserId GetPlatformUserIdForInputDevice(FInputDeviceId InputDevice) const;
/** Gets a user's primary input device id */
virtual FInputDeviceId GetPrimaryInputDeviceForPlatformUser(FPlatformUserId PlatformUser) const;
/** Call from game code to set the cached trait tags when platform state or options changes */
virtual void SetTraitTags(const FGameplayTagContainer& InTags);
/** Gets the current tags that affect feature avialability */
const FGameplayTagContainer& GetTraitTags() const { return CachedTraitTags; }
/** Checks if a specific platform/feature tag is enabled */
UFUNCTION(BlueprintPure, Category=CommonUser)
bool HasTraitTag(const FGameplayTag TraitTag) const { return CachedTraitTags.HasTag(TraitTag); }
/** Checks to see if we should display a press start/input confirmation screen at startup. Games can call this or check the trait tags directly */
UFUNCTION(BlueprintPure, BlueprintPure, Category=CommonUser)
virtual bool ShouldWaitForStartInput() const;
// Functions for accessing low-level online system information
#if COMMONUSER_OSSV1
/** Returns OSS interface of specific type, will return null if there is no type */
IOnlineSubsystem* GetOnlineSubsystem(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
/** Returns identity interface of specific type, will return null if there is no type */
IOnlineIdentity* GetOnlineIdentity(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
/** Returns human readable name of OSS system */
FName GetOnlineSubsystemName(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
/** Returns the current online connection status */
EOnlineServerConnectionStatus::Type GetConnectionStatus(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
#else
/** Get the services provider type, or None if there isn't one. */
UE::Online::EOnlineServices GetOnlineServicesProvider(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
/** Returns auth interface of specific type, will return null if there is no type */
UE::Online::IAuthPtr GetOnlineAuth(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
/** Returns the current online connection status */
UE::Online::EOnlineServicesConnectionStatus GetConnectionStatus(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
#endif
/** Returns true if we are currently connected to backend servers */
bool HasOnlineConnection(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
/** Returns the current login status for a player on the specified online system, only works for real platform users */
ELoginStatusType GetLocalUserLoginStatus(FPlatformUserId PlatformUser, ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
/** Returns the unique net id for a local platform user */
FUniqueNetIdRepl GetLocalUserNetId(FPlatformUserId PlatformUser, ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
/** Returns the nickname for a local platform user, this is cached in common user Info */
FString GetLocalUserNickname(FPlatformUserId PlatformUser, ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
/** Convert a user id to a debug string */
FString PlatformUserIdToString(FPlatformUserId UserId);
/** Convert a context to a debug string */
FString ECommonUserOnlineContextToString(ECommonUserOnlineContext Context);
/** Returns human readable string for privilege checks */
virtual FText GetPrivilegeDescription(ECommonUserPrivilege Privilege) const;
virtual FText GetPrivilegeResultDescription(ECommonUserPrivilegeResult Result) const;
/**
* Starts the process of login for an existing local user, will return false if callback was not scheduled
* This activates the low level state machine and does not modify the initialization state on user info
*/
DECLARE_DELEGATE_FiveParams(FOnLocalUserLoginCompleteDelegate, const UCommonUserInfo* /*UserInfo*/, ELoginStatusType /*NewStatus*/, FUniqueNetIdRepl /*NetId*/, const TOptional<FOnlineErrorType>& /*Error*/, ECommonUserOnlineContext /*Type*/);
virtual bool LoginLocalUser(const UCommonUserInfo* UserInfo, ECommonUserPrivilege RequestedPrivilege, ECommonUserOnlineContext Context, FOnLocalUserLoginCompleteDelegate OnComplete);
/** Assign a local player to a specific local user and call callbacks as needed */
virtual void SetLocalPlayerUserInfo(ULocalPlayer* LocalPlayer, const UCommonUserInfo* UserInfo);
/** Resolves a context that has default behavior into a specific context */
ECommonUserOnlineContext ResolveOnlineContext(ECommonUserOnlineContext Context) const;
/** True if there is a separate platform and service interface */
bool HasSeparatePlatformContext() const;
protected:
/** Internal structure that caches status and pointers for each online context */
struct FOnlineContextCache
{
#if COMMONUSER_OSSV1
/** Pointer to base subsystem, will stay valid as long as game instance does */
IOnlineSubsystem* OnlineSubsystem = nullptr;
/** Cached identity system, this will always be valid */
IOnlineIdentityPtr IdentityInterface;
/** Last connection status that was passed into the HandleNetworkConnectionStatusChanged hander */
EOnlineServerConnectionStatus::Type CurrentConnectionStatus = EOnlineServerConnectionStatus::Normal;
#else
/** Online services, accessor to specific services */
UE::Online::IOnlineServicesPtr OnlineServices;
/** Cached auth service */
UE::Online::IAuthPtr AuthService;
/** Login status changed event handle */
UE::Online::FOnlineEventDelegateHandle LoginStatusChangedHandle;
/** Connection status changed event handle */
UE::Online::FOnlineEventDelegateHandle ConnectionStatusChangedHandle;
/** Last connection status that was passed into the HandleNetworkConnectionStatusChanged hander */
UE::Online::EOnlineServicesConnectionStatus CurrentConnectionStatus = UE::Online::EOnlineServicesConnectionStatus::NotConnected;
#endif
/** Resets state, important to clear all shared ptrs */
void Reset()
{
#if COMMONUSER_OSSV1
OnlineSubsystem = nullptr;
IdentityInterface.Reset();
CurrentConnectionStatus = EOnlineServerConnectionStatus::Normal;
#else
OnlineServices.Reset();
AuthService.Reset();
CurrentConnectionStatus = UE::Online::EOnlineServicesConnectionStatus::NotConnected;
#endif
}
};
/** Internal structure to represent an in-progress login request */
struct FUserLoginRequest : public TSharedFromThis<FUserLoginRequest>
{
FUserLoginRequest(UCommonUserInfo* InUserInfo, ECommonUserPrivilege InPrivilege, ECommonUserOnlineContext InContext, FOnLocalUserLoginCompleteDelegate&& InDelegate)
: UserInfo(TWeakObjectPtr<UCommonUserInfo>(InUserInfo))
, DesiredPrivilege(InPrivilege)
, DesiredContext(InContext)
, Delegate(MoveTemp(InDelegate))
{}
/** Which local user is trying to log on */
TWeakObjectPtr<UCommonUserInfo> UserInfo;
/** Overall state of login request, could come from many sources */
ECommonUserAsyncTaskState OverallLoginState = ECommonUserAsyncTaskState::NotStarted;
/** State of attempt to use platform auth. When started, this immediately transitions to Failed for OSSv1, as we do not support platform auth there. */
ECommonUserAsyncTaskState TransferPlatformAuthState = ECommonUserAsyncTaskState::NotStarted;
/** State of attempt to use AutoLogin */
ECommonUserAsyncTaskState AutoLoginState = ECommonUserAsyncTaskState::NotStarted;
/** State of attempt to use external login UI */
ECommonUserAsyncTaskState LoginUIState = ECommonUserAsyncTaskState::NotStarted;
/** Final privilege to that is requested */
ECommonUserPrivilege DesiredPrivilege = ECommonUserPrivilege::Invalid_Count;
/** State of attempt to request the relevant privilege */
ECommonUserAsyncTaskState PrivilegeCheckState = ECommonUserAsyncTaskState::NotStarted;
/** The final context to log into */
ECommonUserOnlineContext DesiredContext = ECommonUserOnlineContext::Invalid;
/** What online system we are currently logging into */
ECommonUserOnlineContext CurrentContext = ECommonUserOnlineContext::Invalid;
/** User callback for completion */
FOnLocalUserLoginCompleteDelegate Delegate;
/** Most recent/relevant error to display to user */
TOptional<FOnlineErrorType> Error;
};
/** Create a new user info object */
virtual UCommonUserInfo* CreateLocalUserInfo(int32 LocalPlayerIndex);
/** Deconst wrapper for const getters */
FORCEINLINE UCommonUserInfo* ModifyInfo(const UCommonUserInfo* Info) { return const_cast<UCommonUserInfo*>(Info); }
/** Refresh user info from OSS */
virtual void RefreshLocalUserInfo(UCommonUserInfo* UserInfo);
/** Possibly send privilege availability notification, compares current value to cached old value */
virtual void HandleChangedAvailability(UCommonUserInfo* UserInfo, ECommonUserPrivilege Privilege, ECommonUserAvailability OldAvailability);
/** Updates the cached privilege on a user and notifies delegate */
virtual void UpdateUserPrivilegeResult(UCommonUserInfo* UserInfo, ECommonUserPrivilege Privilege, ECommonUserPrivilegeResult Result, ECommonUserOnlineContext Context);
/** Gets internal data for a type of online system, can return null for service */
const FOnlineContextCache* GetContextCache(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game) const;
FOnlineContextCache* GetContextCache(ECommonUserOnlineContext Context = ECommonUserOnlineContext::Game);
/** Create and set up system objects before delegates are bound */
virtual void CreateOnlineContexts();
virtual void DestroyOnlineContexts();
/** Bind online delegates */
virtual void BindOnlineDelegates();
/** Forcibly logs out and deinitializes a single user */
virtual void LogOutLocalUser(FPlatformUserId PlatformUser);
/** Performs the next step of a login request, which could include completing it. Returns true if it's done */
virtual void ProcessLoginRequest(TSharedRef<FUserLoginRequest> Request);
/** Call login on OSS, with platform auth from the platform OSS. Return true if AutoLogin started */
virtual bool TransferPlatformAuth(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
/** Call AutoLogin on OSS. Return true if AutoLogin started. */
virtual bool AutoLogin(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
/** Call ShowLoginUI on OSS. Return true if ShowLoginUI started. */
virtual bool ShowLoginUI(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
/** Call QueryUserPrivilege on OSS. Return true if QueryUserPrivilege started. */
virtual bool QueryUserPrivilege(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
/** OSS-specific functions */
#if COMMONUSER_OSSV1
virtual ECommonUserPrivilege ConvertOSSPrivilege(EUserPrivileges::Type Privilege) const;
virtual EUserPrivileges::Type ConvertOSSPrivilege(ECommonUserPrivilege Privilege) const;
virtual ECommonUserPrivilegeResult ConvertOSSPrivilegeResult(EUserPrivileges::Type Privilege, uint32 Results) const;
void BindOnlineDelegatesOSSv1();
bool AutoLoginOSSv1(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
bool ShowLoginUIOSSv1(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
bool QueryUserPrivilegeOSSv1(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
#else
virtual ECommonUserPrivilege ConvertOnlineServicesPrivilege(UE::Online::EUserPrivileges Privilege) const;
virtual UE::Online::EUserPrivileges ConvertOnlineServicesPrivilege(ECommonUserPrivilege Privilege) const;
virtual ECommonUserPrivilegeResult ConvertOnlineServicesPrivilegeResult(UE::Online::EUserPrivileges Privilege, UE::Online::EPrivilegeResults Results) const;
void BindOnlineDelegatesOSSv2();
void CacheConnectionStatus(ECommonUserOnlineContext Context);
bool TransferPlatformAuthOSSv2(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
bool AutoLoginOSSv2(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
bool ShowLoginUIOSSv2(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
bool QueryUserPrivilegeOSSv2(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser);
TSharedPtr<UE::Online::FAccountInfo> GetOnlineServiceAccountInfo(UE::Online::IAuthPtr AuthService, FPlatformUserId InUserId) const;
#endif
/** Callbacks for OSS functions */
#if COMMONUSER_OSSV1
virtual void HandleIdentityLoginStatusChanged(int32 PlatformUserIndex, ELoginStatus::Type OldStatus, ELoginStatus::Type NewStatus, const FUniqueNetId& NewId, ECommonUserOnlineContext Context);
virtual void HandleUserLoginCompleted(int32 PlatformUserIndex, bool bWasSuccessful, const FUniqueNetId& NetId, const FString& Error, ECommonUserOnlineContext Context);
virtual void HandleControllerPairingChanged(int32 PlatformUserIndex, FControllerPairingChangedUserInfo PreviousUser, FControllerPairingChangedUserInfo NewUser);
virtual void HandleNetworkConnectionStatusChanged(const FString& ServiceName, EOnlineServerConnectionStatus::Type LastConnectionStatus, EOnlineServerConnectionStatus::Type ConnectionStatus, ECommonUserOnlineContext Context);
virtual void HandleOnLoginUIClosed(TSharedPtr<const FUniqueNetId> LoggedInNetId, const int PlatformUserIndex, const FOnlineError& Error, ECommonUserOnlineContext Context);
virtual void HandleCheckPrivilegesComplete(const FUniqueNetId& UserId, EUserPrivileges::Type Privilege, uint32 PrivilegeResults, ECommonUserPrivilege RequestedPrivilege, TWeakObjectPtr<UCommonUserInfo> CommonUserInfo, ECommonUserOnlineContext Context);
#else
virtual void HandleAuthLoginStatusChanged(const UE::Online::FAuthLoginStatusChanged& EventParameters, ECommonUserOnlineContext Context);
virtual void HandleUserLoginCompletedV2(const UE::Online::TOnlineResult<UE::Online::FAuthLogin>& Result, FPlatformUserId PlatformUser, ECommonUserOnlineContext Context);
virtual void HandleOnLoginUIClosedV2(const UE::Online::TOnlineResult<UE::Online::FExternalUIShowLoginUI>& Result, FPlatformUserId PlatformUser, ECommonUserOnlineContext Context);
virtual void HandleNetworkConnectionStatusChanged(const UE::Online::FConnectionStatusChanged& EventParameters, ECommonUserOnlineContext Context);
virtual void HandleCheckPrivilegesComplete(const UE::Online::TOnlineResult<UE::Online::FQueryUserPrivilege>& Result, TWeakObjectPtr<UCommonUserInfo> CommonUserInfo, UE::Online::EUserPrivileges DesiredPrivilege, ECommonUserOnlineContext Context);
#endif
/**
* Callback for when an input device (i.e. a gamepad) has been connected or disconnected.
*/
virtual void HandleInputDeviceConnectionChanged(EInputDeviceConnectionState NewConnectionState, FPlatformUserId PlatformUserId, FInputDeviceId InputDeviceId);
virtual void HandleLoginForUserInitialize(const UCommonUserInfo* UserInfo, ELoginStatusType NewStatus, FUniqueNetIdRepl NetId, const TOptional<FOnlineErrorType>& Error, ECommonUserOnlineContext Context, FCommonUserInitializeParams Params);
virtual void HandleUserInitializeFailed(FCommonUserInitializeParams Params, FText Error);
virtual void HandleUserInitializeSucceeded(FCommonUserInitializeParams Params);
/** Callback for handling press start/login logic */
virtual bool OverrideInputKeyForLogin(FInputKeyEventArgs& EventArgs);
/** Previous override handler, will restore on cancel */
FOverrideInputKeyHandler WrappedInputKeyHandler;
/** List of keys to listen for from any user */
TArray<FKey> LoginKeysForAnyUser;
/** List of keys to listen for a new unmapped user */
TArray<FKey> LoginKeysForNewUser;
/** Params to use for a key-triggered login */
FCommonUserInitializeParams ParamsForLoginKey;
/** Maximum number of local players */
int32 MaxNumberOfLocalPlayers = 0;
/** True if this is a dedicated server, which doesn't require a LocalPlayer */
bool bIsDedicatedServer = false;
/** List of current in progress login requests */
TArray<TSharedRef<FUserLoginRequest>> ActiveLoginRequests;
/** Information about each local user, from local player index to user */
UPROPERTY()
TMap<int32, TObjectPtr<UCommonUserInfo>> LocalUserInfos;
/** Cached platform/mode trait tags */
FGameplayTagContainer CachedTraitTags;
/** Do not access this outside of initialization */
FOnlineContextCache* DefaultContextInternal = nullptr;
FOnlineContextCache* ServiceContextInternal = nullptr;
FOnlineContextCache* PlatformContextInternal = nullptr;
friend UCommonUserInfo;
};

View File

@ -0,0 +1,218 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#if COMMONUSER_OSSV1
// Online Subsystem (OSS v1) includes and forward declares
#include "OnlineSubsystemTypes.h"
class IOnlineSubsystem;
struct FOnlineError;
using FOnlineErrorType = FOnlineError;
using ELoginStatusType = ELoginStatus::Type;
#else
// Online Services (OSS v2) includes and forward declares
#include "Online/Connectivity.h"
#include "Online/OnlineError.h"
namespace UE::Online
{
enum class ELoginStatus : uint8;
enum class EPrivilegeResults : uint32;
enum class EUserPrivileges : uint8;
using IAuthPtr = TSharedPtr<class IAuth>;
using IOnlineServicesPtr = TSharedPtr<class IOnlineServices>;
template <typename OpType>
class TOnlineResult;
struct FAuthLogin;
struct FConnectionStatusChanged;
struct FExternalUIShowLoginUI;
struct FAuthLoginStatusChanged;
struct FQueryUserPrivilege;
struct FAccountInfo;
}
using FOnlineErrorType = UE::Online::FOnlineError;
using ELoginStatusType = UE::Online::ELoginStatus;
#endif
#include "CommonUserTypes.generated.h"
/** Enum specifying where and how to run online queries */
UENUM(BlueprintType)
enum class ECommonUserOnlineContext : uint8
{
/** Called from game code, this uses the default system but with special handling that could merge results from multiple contexts */
Game,
/** The default engine online system, this will always exist and will be the same as either Service or Platform */
Default,
/** Explicitly ask for the external service, which may not exist */
Service,
/** Looks for external service first, then falls back to default */
ServiceOrDefault,
/** Explicitly ask for the platform system, which may not exist */
Platform,
/** Looks for platform system first, then falls back to default */
PlatformOrDefault,
/** Invalid system */
Invalid
};
/** Enum describing the state of initialization for a specific user */
UENUM(BlueprintType)
enum class ECommonUserInitializationState : uint8
{
/** User has not started login process */
Unknown,
/** Player is in the process of acquiring a user id with local login */
DoingInitialLogin,
/** Player is performing the network login, they have already logged in locally */
DoingNetworkLogin,
/** Player failed to log in at all */
FailedtoLogin,
/** Player is logged in and has access to online functionality */
LoggedInOnline,
/** Player is logged in locally (either guest or real user), but cannot perform online actions */
LoggedInLocalOnly,
/** Invalid state or user */
Invalid,
};
/** Enum specifying different privileges and capabilities available to a user */
UENUM(BlueprintType)
enum class ECommonUserPrivilege : uint8
{
/** Whether the user can play at all, online or offline */
CanPlay,
/** Whether the user can play in online modes */
CanPlayOnline,
/** Whether the user can use text chat */
CanCommunicateViaTextOnline,
/** Whether the user can use voice chat */
CanCommunicateViaVoiceOnline,
/** Whether the user can access content generated by other users */
CanUseUserGeneratedContent,
/** Whether the user can ever participate in cross-play */
CanUseCrossPlay,
/** Invalid privilege (also the count of valid ones) */
Invalid_Count UMETA(Hidden)
};
/** Enum specifying the general availability of a feature or privilege, this combines information from multiple sources */
UENUM(BlueprintType)
enum class ECommonUserAvailability : uint8
{
/** State is completely unknown and needs to be queried */
Unknown,
/** This feature is fully available for use right now */
NowAvailable,
/** This might be available after the completion of normal login procedures */
PossiblyAvailable,
/** This feature is not available now because of something like network connectivity but may be available in the future */
CurrentlyUnavailable,
/** This feature will never be available for the rest of this session due to hard account or platform restrictions */
AlwaysUnavailable,
/** Invalid feature */
Invalid,
};
/** Enum giving specific reasons why a user may or may not use a certain privilege */
UENUM(BlueprintType)
enum class ECommonUserPrivilegeResult : uint8
{
/** State is unknown and needs to be queried */
Unknown,
/** This privilege is fully available for use */
Available,
/** User has not fully logged in */
UserNotLoggedIn,
/** User does not own the game or content */
LicenseInvalid,
/** The game needs to be updated or patched before this will be available */
VersionOutdated,
/** No network connection, this may be resolved by reconnecting */
NetworkConnectionUnavailable,
/** Parental control failure */
AgeRestricted,
/** Account does not have a required subscription or account type */
AccountTypeRestricted,
/** Another account/user restriction such as being banned by the service */
AccountUseRestricted,
/** Other platform-specific failure */
PlatformFailure,
};
/** Used to track the progress of different asynchronous operations */
enum class ECommonUserAsyncTaskState : uint8
{
/** The task has not been started */
NotStarted,
/** The task is currently being processed */
InProgress,
/** The task has completed successfully */
Done,
/** The task failed to complete */
Failed
};
/** Detailed information about the online error. Effectively a wrapper for FOnlineError. */
USTRUCT(BlueprintType)
struct FOnlineResultInformation
{
GENERATED_BODY()
/** Whether the operation was successful or not. If it was successful, the error fields of this struct will not contain extra information. */
UPROPERTY(BlueprintReadOnly)
bool bWasSuccessful = true;
/** The unique error id. Can be used to compare against specific handled errors. */
UPROPERTY(BlueprintReadOnly)
FString ErrorId;
/** Error text to display to the user. */
UPROPERTY(BlueprintReadOnly)
FText ErrorText;
/**
* Initialize this from an FOnlineErrorType
* @param InOnlineError the online error to initialize from
*/
void COMMONUSER_API FromOnlineError(const FOnlineErrorType& InOnlineError);
};

View File

@ -9,7 +9,7 @@
#include "GameFeatureAction.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSExperienceActionSet)
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSExperienceActionSetDataAsset)
#define LOCTEXT_NAMESPACE "OLSSystem"

View File

@ -8,7 +8,7 @@
#include "Misc/DataValidation.h"
#endif
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSExperienceDefinitionPrimaryDataAsset)
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSExperienceDefinitionDataAsset)
#define LOCTEXT_NAMESPACE "OLSSystem"

View File

@ -165,7 +165,7 @@ bool UOLSExperienceManagerComponent::IsExperienceLoaded() const
}
void UOLSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded_HighPriority(
FOnOLSExperienceLoaded::FDelegate&& delegate)
FOLSExperienceLoadedNativeDelegate::FDelegate&& delegate)
{
if (IsExperienceLoaded())
{
@ -177,7 +177,7 @@ void UOLSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded_HighPrior
}
}
void UOLSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded(FOnOLSExperienceLoaded::FDelegate&& delegate)
void UOLSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded(FOLSExperienceLoadedNativeDelegate::FDelegate&& delegate)
{
if (IsExperienceLoaded())
{
@ -190,7 +190,7 @@ void UOLSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded(FOnOLSExp
}
void UOLSExperienceManagerComponent::CallOrRegister_OnExperienceLoaded_LowPriority(
FOnOLSExperienceLoaded::FDelegate&& delegate)
FOLSExperienceLoadedNativeDelegate::FDelegate&& delegate)
{
if (IsExperienceLoaded())
{

View File

@ -0,0 +1,529 @@
// © 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 "DataAssets/OLSExperienceDefinitionDataAsset.h"
#include "DataAssets/OLSPawnDataAsset.h"
#include "GameModes/OLSExperienceManagerComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Player/OLSPlayerState.h"
#include "Systems/OLSAssetManager.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSGameMode)
// AOLSGameMode::AOLSGameMode(const FObjectInitializer& objectInitializer) : Super(objectInitializer)
// {
// // @TODO: Implement this.
// // GameStateClass = ALyraGameState::StaticClass();
// // GameSessionClass = ALyraGameSession::StaticClass();
// // PlayerControllerClass = ALyraPlayerController::StaticClass();
// // ReplaySpectatorPlayerControllerClass = ALyraReplayPlayerController::StaticClass();
// // PlayerStateClass = ALyraPlayerState::StaticClass();
// // DefaultPawnClass = ALyraCharacter::StaticClass();
// // HUDClass = ALyraHUD::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);
}
}

View File

@ -6,6 +6,8 @@
#include "AbilitySystem/OLSAbilitySystemComponent.h"
#include "Components/GameFrameworkComponentManager.h"
#include "DataAssets/OLSAbilitySetPrimaryDataAsset.h"
#include "GameModes/OLSExperienceManagerComponent.h"
#include "GameModes/OLSGameMode.h"
#include "Net/UnrealNetwork.h"
#include "Player/OLSPlayerController.h"
@ -18,12 +20,6 @@ AOLSPlayerState::AOLSPlayerState(const FObjectInitializer& objectInitializer) :
// Create attribute sets here.
}
template <class T>
const T* AOLSPlayerState::GetPawnData() const
{
return Cast<T>(PawnData);
}
void AOLSPlayerState::SetPawnData(const UOLSPawnDataAsset* pawnData)
{
check(pawnData);
@ -55,6 +51,42 @@ void AOLSPlayerState::SetPawnData(const UOLSPawnDataAsset* pawnData)
ForceNetUpdate();
}
void AOLSPlayerState::AddStatTagStack(FGameplayTag tag, int32 stackCount)
{
StatTags.AddStack(tag, stackCount);
}
void AOLSPlayerState::RemoveStatTagStack(FGameplayTag tag, int32 stackCount)
{
StatTags.RemoveStack(tag, stackCount);
}
int32 AOLSPlayerState::GetStatTagStackCount(FGameplayTag tag) const
{
return StatTags.GetStackCount(tag);
}
bool AOLSPlayerState::HasStatTag(FGameplayTag tag) const
{
return StatTags.ContainsTag(tag);
}
void AOLSPlayerState::OnExperienceLoaded(const UOLSExperienceDefinitionDataAsset* currentExperience)
{
if (AOLSGameMode* gameMode = GetWorld()->GetAuthGameMode<AOLSGameMode>())
{
if (const UOLSPawnDataAsset* newPawnData = gameMode->GetPawnDataForController(GetOwningController()))
{
SetPawnData(newPawnData);
}
else
{
// @T ODO: Replace this with our custom.
// UE_LOG(LogLyra, Error, TEXT("ALyraPlayerState::OnExperienceLoaded(): Unable to find PawnData to initialize player state [%s]!"), *GetNameSafe(this));
}
}
}
void AOLSPlayerState::OnRep_PawnData()
{
}
@ -71,9 +103,9 @@ void AOLSPlayerState::PostInitializeComponents()
{
const TObjectPtr<AGameStateBase> gameState = GetWorld()->GetGameState();
check(gameState);
// ULyraExperienceManagerComponent* ExperienceComponent = GameState->FindComponentByClass<ULyraExperienceManagerComponent>();
// check(ExperienceComponent);
// ExperienceComponent->CallOrRegister_OnExperienceLoaded(FOnLyraExperienceLoaded::FDelegate::CreateUObject(this, &ThisClass::OnExperienceLoaded));
UOLSExperienceManagerComponent* ExperienceComponent = gameState->FindComponentByClass<UOLSExperienceManagerComponent>();
check(ExperienceComponent);
ExperienceComponent->CallOrRegister_OnExperienceLoaded(FOLSExperienceLoadedNativeDelegate::FDelegate::CreateUObject(this, &ThisClass::OnExperienceLoaded));
}
}

View File

@ -7,7 +7,7 @@
#include "Misc/ScopedSlowTask.h"
#include "Engine/Engine.h"
#include "Systems/OLSAssetManagerStartupJob.h"
#include "DataAssets/OLSPawnPrimaryDataAsset.h"
#include "DataAssets/OLSPawnDataAsset.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSAssetManager)
@ -105,7 +105,7 @@ void UOLSAssetManager::DumpLoadedAssets()
{
}
const UOLSPawnPrimaryDataAsset* UOLSAssetManager::GetDefaultPawnData() const
const UOLSPawnDataAsset* UOLSAssetManager::GetDefaultPawnData() const
{
return GetAsset(DefaultPawnData);
}

View File

@ -0,0 +1,104 @@
// © 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 "Systems/OLSGameplayTagStack.h"
//////////////////////////////////////////////////////////////////////
// FGameplayTagStack
FString FOLSGameplayTagStack::GetDebugString() const
{
return FString::Printf(TEXT("%sx%d"), *Tag.ToString(), StackCount);
}
//////////////////////////////////////////////////////////////////////
// FGameplayTagStackContainer
void FOLSGameplayTagStackContainer::AddStack(FGameplayTag tag, int32 stackCount)
{
if (!tag.IsValid())
{
FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to AddStack"), ELogVerbosity::Warning);
return;
}
if (stackCount > 0)
{
for (FOLSGameplayTagStack& stack : Stacks)
{
if (stack.Tag == tag)
{
const int32 newCount = stack.StackCount + stackCount;
stack.StackCount = newCount;
TagToCountMap[tag] = newCount;
MarkItemDirty(stack);
return;
}
}
FOLSGameplayTagStack& newStack = Stacks.Emplace_GetRef(tag, stackCount);
MarkItemDirty(newStack);
TagToCountMap.Add(tag, stackCount);
}
}
void FOLSGameplayTagStackContainer::RemoveStack(FGameplayTag tag, int32 stackCount)
{
if (!tag.IsValid())
{
FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to RemoveStack"), ELogVerbosity::Warning);
return;
}
//@TODO: Should we error if you try to remove a stack that doesn't exist or has a smaller count?
if (stackCount > 0)
{
for (auto it = Stacks.CreateIterator(); it; ++it)
{
FOLSGameplayTagStack& stack = *it;
if (stack.Tag == tag)
{
if (stack.StackCount <= stackCount)
{
it.RemoveCurrent();
TagToCountMap.Remove(tag);
MarkArrayDirty();
}
else
{
const int32 newCount = stack.StackCount - stackCount;
stack.StackCount = newCount;
TagToCountMap[tag] = newCount;
MarkItemDirty(stack);
}
return;
}
}
}
}
void FOLSGameplayTagStackContainer::PreReplicatedRemove(const TArrayView<int32> removedIndices, int32 finalSize)
{
for (int32 index : removedIndices)
{
const FGameplayTag tag = Stacks[index].Tag;
TagToCountMap.Remove(tag);
}
}
void FOLSGameplayTagStackContainer::PostReplicatedAdd(const TArrayView<int32> addedIndices, int32 finalSize)
{
for (int32 index : addedIndices)
{
const FOLSGameplayTagStack& stack = Stacks[index];
TagToCountMap.Add(stack.Tag, stack.StackCount);
}
}
void FOLSGameplayTagStackContainer::PostReplicatedChange(const TArrayView<int32> changedIndices, int32 finalSize)
{
for (int32 index : changedIndices)
{
const FOLSGameplayTagStack& stack = Stacks[index];
TagToCountMap[stack.Tag] = stack.StackCount;
}
}

View File

@ -9,7 +9,7 @@
namespace UE::GameFeatures { struct FResult; }
DECLARE_MULTICAST_DELEGATE_OneParam(FOnOLSExperienceLoaded, const class UOLSExperienceDefinitionDataAsset* /*experience*/);
DECLARE_MULTICAST_DELEGATE_OneParam(FOLSExperienceLoadedNativeDelegate, const class UOLSExperienceDefinitionDataAsset* /*experience*/);
enum class EOLSExperienceLoadState
{
@ -55,15 +55,15 @@ public:
// Ensures the delegate is called once the experience has been loaded,
// before others are called.
// However, if the experience has already loaded, calls the delegate immediately.
void CallOrRegister_OnExperienceLoaded_HighPriority(FOnOLSExperienceLoaded::FDelegate&& delegate);
void CallOrRegister_OnExperienceLoaded_HighPriority(FOLSExperienceLoadedNativeDelegate::FDelegate&& delegate);
// Ensures the delegate is called once the experience has been loaded
// If the experience has already loaded, calls the delegate immediately
void CallOrRegister_OnExperienceLoaded(FOnOLSExperienceLoaded::FDelegate&& delegate);
void CallOrRegister_OnExperienceLoaded(FOLSExperienceLoadedNativeDelegate::FDelegate&& delegate);
// Ensures the delegate is called once the experience has been loaded
// If the experience has already loaded, calls the delegate immediately
void CallOrRegister_OnExperienceLoaded_LowPriority(FOnOLSExperienceLoaded::FDelegate&& delegate);
void CallOrRegister_OnExperienceLoaded_LowPriority(FOLSExperienceLoadedNativeDelegate::FDelegate&& delegate);
private:
@ -99,11 +99,11 @@ private:
* Delegate called when the experience has finished loading just before others
* (e.g., subsystems that set up for regular gameplay)
*/
FOnOLSExperienceLoaded OnExperienceLoaded_HighPriority;
FOLSExperienceLoadedNativeDelegate OnExperienceLoaded_HighPriority;
/** Delegate called when the experience has finished loading */
FOnOLSExperienceLoaded OnExperienceLoaded;
FOLSExperienceLoadedNativeDelegate OnExperienceLoaded;
/** Delegate called when the experience has finished loading */
FOnOLSExperienceLoaded OnExperienceLoaded_LowPriority;
FOLSExperienceLoadedNativeDelegate OnExperienceLoaded_LowPriority;
};

View File

@ -0,0 +1,77 @@
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
#pragma once
#include "CoreMinimal.h"
#include "CommonSessionSubsystem.h"
#include "ModularGameplayActors/OLSModularGameMode.h"
#include "OLSGameMode.generated.h"
/**
* Post login event, triggered when a player or bot joins the game as well as after seamless and non seamless travel
*
* This is called after the player has finished initialization
*/
DECLARE_MULTICAST_DELEGATE_TwoParams(FOLSGameModePlayerInitializeNativeDelegate, class AGameModeBase* /*GameMode*/,
class AController* /*NewPlayer*/);
/**
* AOLSGameMode
*
* The base game mode class used by this project.
*/
UCLASS(Config = Game, Meta = (ShortTooltip = "The base game mode class used by this project."))
class OLS_API AOLSGameMode : public AOLSModularGameModeBase
{
GENERATED_BODY()
public:
// AOLSGameMode(const FObjectInitializer& objectInitializer);
UFUNCTION(BlueprintCallable, Category = "OLS|Pawn")
const class UOLSPawnDataAsset* GetPawnDataForController(const class AController* controller) const;
//~ Begin AGameModeBase interface
virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override;
virtual UClass* GetDefaultPawnClassForController_Implementation(AController* controller) override;
virtual APawn* SpawnDefaultPawnAtTransform_Implementation(AController* newPlayer, const FTransform& spawnTransform) override;
virtual bool ShouldSpawnAtStartSpot(AController* Player) override;
virtual void HandleStartingNewPlayer_Implementation(APlayerController* newPlayer) override;
virtual AActor* ChoosePlayerStart_Implementation(AController* player) override;
virtual void FinishRestartPlayer(AController* newPlayer, const FRotator& startRotation) override;
virtual bool PlayerCanRestart_Implementation(APlayerController* player) override;
virtual void InitGameState() override;
virtual bool UpdatePlayerStartSpot(AController* player, const FString& portal, FString& outErrorMessage) override;
virtual void GenericPlayerInitialization(AController* newPlayer) override;
virtual void FailedToRestartPlayer(AController* newPlayer) override;
//~ End AGameModeBase interface
// Restart (respawn) the specified player or bot next frame
// - If bForceReset is true, the controller will be reset this frame (abandoning the currently possessed pawn, if any)
UFUNCTION(BlueprintCallable)
void RequestPlayerRestartNextFrame(AController* controller, bool shouldForceReset = false);
// Agnostic version of PlayerCanRestart that can be used for both player bots and players
virtual bool ControllerCanRestart(AController* controller);
// Delegate called on player initialization, described above
FOLSGameModePlayerInitializeNativeDelegate OnGameModePlayerInitialized;
protected:
void OnExperienceLoaded(const class UOLSExperienceDefinitionDataAsset* currentExperience);
bool IsExperienceLoaded() const;
void OnMatchAssignmentGiven(FPrimaryAssetId experienceId, const FString& experienceIdSource);
void HandleMatchAssignmentIfNotExpectingOne();
bool TryDedicatedServerLogin();
void HostDedicatedServerMatch(ECommonSessionOnlineMode onlineMode);
UFUNCTION()
void OnUserInitializedForDedicatedServer(const UCommonUserInfo* userInfo, bool isSuccess, FText error, ECommonUserPrivilege requestedPrivilege, ECommonUserOnlineContext onlineContext);
};

View File

@ -4,6 +4,7 @@
#include "CoreMinimal.h"
#include "ModularGameplayActors/OLSModularPlayerState.h"
#include "Systems/OLSGameplayTagStack.h"
#include "OLSPlayerState.generated.h"
@ -37,10 +38,39 @@ public:
class UOLSAbilitySystemComponent* GetOLSAbilitySystemComponent() const;
template <class T>
const T* GetPawnData() const;
const T* GetPawnData() const
{
return Cast<T>(PawnData);
}
void SetPawnData(const class UOLSPawnDataAsset* pawnData);
public:
//////////////////////////////////////////////////////////////////////
// Stats
// Adds a specified number of stacks to the tag (does nothing if StackCount is below 1)
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category= "OLS|Stats")
void AddStatTagStack(FGameplayTag tag, int32 stackCount);
// Removes a specified number of stacks from the tag (does nothing if StackCount is below 1)
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category= "OLS|Stats")
void RemoveStatTagStack(FGameplayTag tag, int32 stackCount);
// Returns the stack count of the specified tag (or 0 if the tag is not present)
UFUNCTION(BlueprintCallable, Category= "OLS|Stats")
int32 GetStatTagStackCount(FGameplayTag tag) const;
// Returns true if there is at least one stack of the specified tag
UFUNCTION(BlueprintCallable, Category= "OLS|Stats")
bool HasStatTag(FGameplayTag tag) const;
private:
void OnExperienceLoaded(const class UOLSExperienceDefinitionDataAsset* currentExperience);
protected:
UFUNCTION()
@ -50,4 +80,9 @@ protected:
UPROPERTY(ReplicatedUsing = OnRep_PawnData)
TObjectPtr<const class UOLSPawnDataAsset> PawnData = nullptr;
private:
UPROPERTY(Replicated)
FOLSGameplayTagStackContainer StatTags;
};

View File

@ -44,7 +44,7 @@ public:
// @Todo implement this function.
// const class UOLSPawnPrimaryDataAsset& GetGameData();
const class UOLSPawnPrimaryDataAsset* GetDefaultPawnData() const;
const class UOLSPawnDataAsset* GetDefaultPawnData() const;
protected:
@ -93,7 +93,7 @@ protected:
// Pawn data used when spawning player pawns if there isn't one set on the player state.
UPROPERTY(Config)
TSoftObjectPtr<class UOLSPawnPrimaryDataAsset> DefaultPawnData;
TSoftObjectPtr<class UOLSPawnDataAsset> DefaultPawnData;
private:

View File

@ -0,0 +1,97 @@
// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
#pragma once
#include "GameplayTagContainer.h"
#include "Net/Serialization/FastArraySerializer.h"
#include "OLSGameplayTagStack.generated.h"
/**
* Represents one stack of a gameplay tag (tag + count)
*/
USTRUCT(BlueprintType)
struct OLS_API FOLSGameplayTagStack : public FFastArraySerializerItem
{
GENERATED_BODY()
FOLSGameplayTagStack()
{}
FOLSGameplayTagStack(FGameplayTag InTag, int32 InStackCount)
: Tag(InTag)
, StackCount(InStackCount)
{
}
FString GetDebugString() const;
private:
friend struct FOLSGameplayTagStackContainer;
UPROPERTY()
FGameplayTag Tag = FGameplayTag::EmptyTag;
UPROPERTY()
int32 StackCount = 0;
};
/** Container of gameplay tag stacks */
USTRUCT(BlueprintType)
struct OLS_API FOLSGameplayTagStackContainer : public FFastArraySerializer
{
GENERATED_BODY()
FOLSGameplayTagStackContainer()
// : Owner(nullptr)
{
}
public:
// Adds a specified number of stacks to the tag (does nothing if StackCount is below 1)
void AddStack(FGameplayTag tag, int32 stackCount);
// Removes a specified number of stacks from the tag (does nothing if StackCount is below 1)
void RemoveStack(FGameplayTag tag, int32 stackCount);
// Returns the stack count of the specified tag (or 0 if the tag is not present)
int32 GetStackCount(FGameplayTag Tag) const
{
return TagToCountMap.FindRef(Tag);
}
// Returns true if there is at least one stack of the specified tag
bool ContainsTag(FGameplayTag Tag) const
{
return TagToCountMap.Contains(Tag);
}
//~ Begin FFastArraySerializer contract
void PreReplicatedRemove(const TArrayView<int32> removedIndices, int32 finalSize);
void PostReplicatedAdd(const TArrayView<int32> addedIndices, int32 finalSize);
void PostReplicatedChange(const TArrayView<int32> changedIndices, int32 finalSize);
//~ End FFastArraySerializer contract
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
{
return FFastArraySerializer::FastArrayDeltaSerialize<FOLSGameplayTagStack, FOLSGameplayTagStackContainer>(Stacks, DeltaParms, *this);
}
private:
// Replicated list of gameplay tag stacks
UPROPERTY()
TArray<FOLSGameplayTagStack> Stacks;
// Accelerated list of tag stacks for queries
TMap<FGameplayTag, int32> TagToCountMap;
};
template<>
struct TStructOpsTypeTraits<FOLSGameplayTagStackContainer> : public TStructOpsTypeTraitsBase2<FOLSGameplayTagStackContainer>
{
enum
{
WithNetDeltaSerializer = true,
};
};

View File

@ -19,7 +19,7 @@ public class ols : ModuleRules
"GameFeatures",
"ModularGameplay",
"EnhancedInput",
"OLSAnimation", "AIModule", "CommonLoadingScreen",
"OLSAnimation", "AIModule", "CommonLoadingScreen", "CommonUser"
});
PrivateDependencyModuleNames.AddRange(new[]