// Copyright Epic Games, Inc. All Rights Reserved. #include "OnlineAsyncTaskManagerSteam.h" #include "OnlinePresenceInterfaceSteam.h" #include "OnlineSessionAsyncLobbySteam.h" #include "OnlineSessionAsyncServerSteam.h" #include "OnlineLeaderboardInterfaceSteam.h" #include "OnlineExternalUIInterfaceSteam.h" #include "OnlineAuthInterfaceSteam.h" #include "SteamUtilities.h" void FOnlineAsyncTaskManagerSteam::OnlineTick() { check(SteamSubsystem); check(FPlatformTLS::GetCurrentThreadId() == OnlineThreadId); if (SteamSubsystem->IsSteamClientAvailable()) { SteamAPI_RunCallbacks(); } if (SteamSubsystem->IsSteamServerAvailable()) { SteamGameServer_RunCallbacks(); } } /** * Event triggered by Steam backend when a user attempts JIP or accepts an invite request (via Steam client) * * @param CallbackData All the valid data from Steam related to this event */ void FOnlineAsyncTaskManagerSteam::OnInviteAccepted(GameRichPresenceJoinRequested_t* CallbackData) { FOnlineAsyncEventSteamInviteAccepted* NewEvent = new FOnlineAsyncEventSteamInviteAccepted(SteamSubsystem, *FUniqueNetIdSteam::Create(CallbackData->m_steamIDFriend), UTF8_TO_TCHAR(CallbackData->m_rgchConnect)); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } /** * Event triggered by Steam backend when a user attempts JIP (via Steam client) or accepts an invite request (via Steam client) * * @param CallbackData All the valid data from Steam related to this event */ void FOnlineAsyncTaskManagerSteam::OnLobbyInviteAccepted(GameLobbyJoinRequested_t* CallbackData) { if (CallbackData->m_steamIDLobby.IsLobby()) { const FUniqueNetIdSteamRef LobbyId = FUniqueNetIdSteam::Create(CallbackData->m_steamIDLobby); FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr(SteamSubsystem->GetSessionInterface()); if (SessionInt.IsValid() && !SessionInt->IsMemberOfLobby(*LobbyId)) { FOnlineAsyncEventSteamLobbyInviteAccepted* NewEvent = new FOnlineAsyncEventSteamLobbyInviteAccepted(SteamSubsystem, *FUniqueNetIdSteam::Create(CallbackData->m_steamIDFriend), *LobbyId); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } else { UE_LOG_ONLINE(Warning, TEXT("Attempting to accept invite to lobby user is already in, ignoring.")); } } else { UE_LOG_ONLINE(Warning, TEXT("OnLobbyInviteAccepted: Invalid LobbyId received.")); } } /** * Notification event from Steam that the lobby state has changed (users joining/leaving) */ class FOnlineAsyncEventSteamLobbyEnter : public FOnlineAsyncEvent { private: /** Lobby enter state information */ LobbyEnter_t CallbackResults; /** Hidden on purpose */ FOnlineAsyncEventSteamLobbyEnter() : FOnlineAsyncEvent(NULL) { FMemory::Memzero(CallbackResults); } public: FOnlineAsyncEventSteamLobbyEnter(FOnlineSubsystemSteam* InSubsystem, const LobbyEnter_t& InResults) : FOnlineAsyncEvent(InSubsystem), CallbackResults(InResults) { } virtual ~FOnlineAsyncEventSteamLobbyEnter() { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamLobbyEnter LobbyId: %s Result: %s"), *FUniqueNetIdSteam::ToDebugString(CSteamID(CallbackResults.m_ulSteamIDLobby)), *SteamChatRoomEnterResponseString((EChatRoomEnterResponse)CallbackResults.m_EChatRoomEnterResponse)); } /** * Give the async task a chance to marshal its data back to the game thread * Can only be called on the game thread by the async task manager */ virtual void Finalize() override { FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr(Subsystem->GetSessionInterface()); if (SessionInt.IsValid()) { const FUniqueNetIdSteamRef LobbyId = FUniqueNetIdSteam::Create(CallbackResults.m_ulSteamIDLobby); const FNamedOnlineSession* Session = SessionInt->GetNamedSessionFromLobbyId(*LobbyId); if (!Session) { UE_LOG_ONLINE(Warning, TEXT("Entered lobby %s, but not found in sessions list"), *LobbyId->ToDebugString()); } } } }; /** * Event triggered by Steam backend when a user joins a lobby * * @param CallbackData All the valid data from Steam related to this event */ void FOnlineAsyncTaskManagerSteam::OnLobbyEnter(LobbyEnter_t* CallbackData) { // The owner of the created lobby shouldn't need this information if (SteamMatchmaking()->GetLobbyOwner(CallbackData->m_ulSteamIDLobby) != SteamUser()->GetSteamID()) { FOnlineAsyncEventSteamLobbyEnter* NewEvent = new FOnlineAsyncEventSteamLobbyEnter(SteamSubsystem, *CallbackData); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } } /** * Notification event from Steam that the lobby state has changed (users joining/leaving) */ class FOnlineAsyncEventSteamLobbyChatUpdate : public FOnlineAsyncEvent { private: /** Lobby chat state information */ LobbyChatUpdate_t CallbackResults; /** Hidden on purpose */ FOnlineAsyncEventSteamLobbyChatUpdate() : FOnlineAsyncEvent(NULL) { FMemory::Memzero(CallbackResults); } public: FOnlineAsyncEventSteamLobbyChatUpdate(FOnlineSubsystemSteam* InSubsystem, const LobbyChatUpdate_t& InResults) : FOnlineAsyncEvent(InSubsystem), CallbackResults(InResults) { } virtual ~FOnlineAsyncEventSteamLobbyChatUpdate() { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamLobbyChatUpdate User: %s Instigator: %s Result: %s"), *FUniqueNetIdSteam::ToDebugString(CSteamID(CallbackResults.m_ulSteamIDUserChanged)), *FUniqueNetIdSteam::ToDebugString(CSteamID(CallbackResults.m_ulSteamIDMakingChange)), *SteamChatMemberStateChangeString((EChatMemberStateChange)CallbackResults.m_rgfChatMemberStateChange)); } /** * Give the async task a chance to marshal its data back to the game thread * Can only be called on the game thread by the async task manager */ virtual void Finalize() override { FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr(Subsystem->GetSessionInterface()); if (SessionInt.IsValid()) { FUniqueNetIdSteamRef LobbyId = FUniqueNetIdSteam::Create(CallbackResults.m_ulSteamIDLobby); // Lobby data update for existing session FNamedOnlineSession* Session = SessionInt->GetNamedSessionFromLobbyId(*LobbyId); if (Session) { // Recreate the lobby member list if (!FillMembersFromLobbyData(*LobbyId, *Session)) { UE_LOG_ONLINE(Warning, TEXT("Failed to parse session %s member update %s"), *Session->SessionName.ToString(), *LobbyId->ToDebugString()); } } else { UE_LOG_ONLINE(Warning, TEXT("Received lobby chat update %s, but not found in sessions list"), *LobbyId->ToDebugString()); } } } }; /** * Event triggered by Steam backend when a user joins a lobby * * @param CallbackData All the valid data from Steam related to this event */ void FOnlineAsyncTaskManagerSteam::OnLobbyChatUpdate(LobbyChatUpdate_t* CallbackData) { FOnlineAsyncEventSteamLobbyChatUpdate* NewEvent = new FOnlineAsyncEventSteamLobbyChatUpdate(SteamSubsystem, *CallbackData); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } /** * Notification event from Steam when new lobby data is available for the given lobby */ class FOnlineAsyncEventSteamLobbyUpdate : public FOnlineAsyncEvent { private: /** Id of lobby to update */ FUniqueNetIdSteamRef LobbyId; /** Hidden on purpose */ FOnlineAsyncEventSteamLobbyUpdate() = delete; public: FOnlineAsyncEventSteamLobbyUpdate(FOnlineSubsystemSteam* InSubsystem, const FUniqueNetIdSteam& InLobbyId) : FOnlineAsyncEvent(InSubsystem), LobbyId(InLobbyId.AsShared()) { } virtual ~FOnlineAsyncEventSteamLobbyUpdate() { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamLobbyUpdate LobbyId: %s"), *LobbyId->ToDebugString()); } /** * Give the async task a chance to marshal its data back to the game thread * Can only be called on the game thread by the async task manager */ virtual void Finalize() override { FOnlineAsyncEvent::Finalize(); // Searching for lobbies case (NULL CurrentSessionSearch implies no active search query) FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr(Subsystem->GetSessionInterface()); if (SessionInt.IsValid() && SessionInt->CurrentSessionSearch.IsValid() && SessionInt->CurrentSessionSearch->SearchState == EOnlineAsyncTaskState::InProgress) { // Add this lobby as available for adding to search results SessionInt->PendingSearchLobbyIds.AddUnique(LobbyId); } else { // Lobby data update for existing session FNamedOnlineSession* Session = SessionInt->GetNamedSessionFromLobbyId(*LobbyId); if (Session) { // Make sure the session has all the valid session data if (!FillSessionFromLobbyData(Subsystem, *LobbyId, *Session) || !FillMembersFromLobbyData(*LobbyId, *Session)) { UE_LOG_ONLINE(Warning, TEXT("Failed to parse session %s lobby update %s"), *Session->SessionName.ToString(), *LobbyId->ToDebugString()); } } else { UE_LOG_ONLINE(Warning, TEXT("Received lobby update %s, but not found in sessions list"), *LobbyId->ToDebugString()); } } } }; /** * Event triggered by Steam backend when new lobby data is available for the given lobby * Can occur any time host calls SetLobbyData or while searching for lobbies (calls to RequestLobbyData) * * @param CallbackData All the valid data from Steam related to this event */ void FOnlineAsyncTaskManagerSteam::OnLobbyDataUpdate(LobbyDataUpdate_t* CallbackData) { // Equivalent lobby ids implies it is lobby data that has updated if (CallbackData->m_ulSteamIDLobby == CallbackData->m_ulSteamIDMember) { if (!CallbackData->m_bSuccess) { // CallbackData->m_bSuccess indicates LobbyID has shut down since // the result was returned but we have to keep the array size in sync UE_LOG_ONLINE(Verbose, TEXT("Lobby %s is no longer available."), *FUniqueNetIdSteam::ToDebugString(CallbackData->m_ulSteamIDLobby)); } ISteamMatchmaking* SteamMatchmakingPtr = SteamMatchmaking(); check(SteamMatchmakingPtr); // The owner of the created lobby shouldn't need this information if (SteamMatchmakingPtr->GetLobbyOwner(CallbackData->m_ulSteamIDLobby) != SteamUser()->GetSteamID()) { FOnlineAsyncEventSteamLobbyUpdate* NewEvent = new FOnlineAsyncEventSteamLobbyUpdate(SteamSubsystem, *FUniqueNetIdSteam::Create(CallbackData->m_ulSteamIDLobby)); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } } else { // @TODO ONLINE - Player data update //const TCHAR* Key = MemberKeyList(i); //const ANSICHAR* MemberValue = SteamMatchmakingPtr->GetLobbyMemberData(CallbackData->m_ulSteamIDLobby, CallbackData->m_ulSteamIDMember, TCHAR_TO_UTF8(Key)); } // @TODO ONLINE - SetLobbyOwner triggers this call also } /** * Notification event from Steam that a given user's * stats/achievements data has been downloaded from the server */ class FOnlineAsyncEventSteamStatsReceived : public FOnlineAsyncEvent { private: /** User this data is for */ const FUniqueNetIdSteamRef UserId; /** Result of the download */ EResult StatsReceivedResult; /** Hidden on purpose */ FOnlineAsyncEventSteamStatsReceived() = delete; public: FOnlineAsyncEventSteamStatsReceived(FOnlineSubsystemSteam* InSubsystem, const FUniqueNetIdSteam& InUserId, EResult InResult) : FOnlineAsyncEvent(InSubsystem), UserId(InUserId.AsShared()), StatsReceivedResult(InResult) { } virtual ~FOnlineAsyncEventSteamStatsReceived() { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamStatsReceived bWasSuccessful: %d User: %s Result: %s"), (StatsReceivedResult == k_EResultOK) ? 1 : 0, *UserId->ToDebugString(), *SteamResultString(StatsReceivedResult)); } }; /** * Event triggered from Steam when the current user's stats have been downloaded from the backend * Possible that the result fails if they have no data for the current game * * @param CallbackData All the valid data from Steam related to this event */ void FOnlineAsyncTaskManagerSteam::OnUserStatsReceived(UserStatsReceived_t* CallbackData) { const CGameID GameID(SteamSubsystem->GetSteamAppId()); if (GameID.ToUint64() == CallbackData->m_nGameID) { const FUniqueNetIdSteamRef UserId = FUniqueNetIdSteam::Create(CallbackData->m_steamIDUser); if (CallbackData->m_eResult != k_EResultOK) { if (CallbackData->m_eResult == k_EResultFail) { UE_LOG_ONLINE(Warning, TEXT("Failed to obtain steam user stats, user: %s has no stats entries"), *UserId->ToDebugString()); } else { UE_LOG_ONLINE(Warning, TEXT("Failed to obtain steam user stats, user: %s error: %s"), *UserId->ToDebugString(), *SteamResultString(CallbackData->m_eResult)); } } FOnlineAsyncEventSteamStatsReceived* NewEvent = new FOnlineAsyncEventSteamStatsReceived(SteamSubsystem, *UserId, CallbackData->m_eResult); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } else { UE_LOG_ONLINE(Warning, TEXT("Obtained steam user stats, but for wrong game! Ignoring.")); } } /** * Notification event from Steam that the currently logged in user's * stats/achievements data has been stored with the server */ class FOnlineAsyncEventSteamStatsStored : public FOnlineAsyncEvent { private: /** User this data is for */ const FUniqueNetIdSteamRef UserId; /** Result of the download */ EResult StatsStoredResult; /** Hidden on purpose */ FOnlineAsyncEventSteamStatsStored() = delete; public: FOnlineAsyncEventSteamStatsStored(FOnlineSubsystemSteam* InSubsystem, const FUniqueNetIdSteam& InUserId, EResult InResult) : FOnlineAsyncEvent(InSubsystem), UserId(InUserId.AsShared()), StatsStoredResult(InResult) { } virtual ~FOnlineAsyncEventSteamStatsStored() { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamStatsStored bWasSuccessful: %d User: %s Result: %s"), (StatsStoredResult == k_EResultOK) ? 1 : 0, *UserId->ToDebugString(), *SteamResultString(StatsStoredResult)); } /** * Give the async task a chance to marshal its data back to the game thread * Can only be called on the game thread by the async task manager */ virtual void Finalize() override { FOnlineAsyncEvent::Finalize(); FOnlineLeaderboardsSteamPtr Leaderboards = StaticCastSharedPtr(Subsystem->GetLeaderboardsInterface()); Leaderboards->UserStatsStoreStatsFinishedDelegate.ExecuteIfBound(StatsStoredResult == k_EResultOK ? EOnlineAsyncTaskState::Done : EOnlineAsyncTaskState::Failed); } }; /** * Event triggered from Steam when the current user's stats have been stored with the backend * Possible that the result fails with "Invalid Param" meaning the stats went out of range or were out of date * New stats are downloaded in this case and need to be re-evaluated * * @param CallbackData All the valid data from Steam related to this event */ void FOnlineAsyncTaskManagerSteam::OnUserStatsStored(UserStatsStored_t* CallbackData) { const CGameID GameID(SteamSubsystem->GetSteamAppId()); if (GameID.ToUint64() == CallbackData->m_nGameID) { // Only the current user comes through this way (other user's stats are stored via GameServerStats) const FUniqueNetIdSteamRef UserId = FUniqueNetIdSteam::Create(SteamUser()->GetSteamID()); if (CallbackData->m_eResult != k_EResultOK) { if (CallbackData->m_eResult == k_EResultInvalidParam) { UE_LOG_ONLINE(Warning, TEXT("Invalid stats data set, stats have been reverted to state prior to last write.")); } else { UE_LOG_ONLINE(Warning, TEXT("Failed to store steam user stats, error: %s"), *SteamResultString(CallbackData->m_eResult)); } } FOnlineAsyncEventSteamStatsStored* NewEvent = new FOnlineAsyncEventSteamStatsStored(SteamSubsystem, *UserId, CallbackData->m_eResult); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } else { UE_LOG_ONLINE(Warning, TEXT("Stored steam user stats, but for wrong game! Ignoring.")); } } /** * Notification event from Steam that the currently logged in user's * stats/achievements data has been stored with the server * FROM VALVE: Steam stats for other users are kept in an LRU with a max queue length of 100 */ class FOnlineAsyncEventSteamStatsUnloaded : public FOnlineAsyncEvent { private: /** User whose data has been unloaded */ FUniqueNetIdSteamRef UserId; /** Hidden on purpose */ FOnlineAsyncEventSteamStatsUnloaded() = delete; public: FOnlineAsyncEventSteamStatsUnloaded(FOnlineSubsystemSteam* InSubsystem, const FUniqueNetIdSteam& InUserId) : FOnlineAsyncEvent(InSubsystem), UserId(InUserId.AsShared()) { } virtual ~FOnlineAsyncEventSteamStatsUnloaded() { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamStatsUnloaded UserId: %s"), *UserId->ToDebugString()); } }; /** * Event triggered from Steam when a previously requested user's stats have been purged in LRU fashion * Requesting the data an additional time will bring the data back * * @param CallbackData All the valid data from Steam related to this event */ void FOnlineAsyncTaskManagerSteam::OnUserStatsUnloaded(UserStatsUnloaded_t* CallbackData) { FOnlineAsyncEventSteamStatsUnloaded* NewEvent = new FOnlineAsyncEventSteamStatsUnloaded(SteamSubsystem, *FUniqueNetIdSteam::Create(CallbackData->m_steamIDUser)); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } /** * Delegate registered with Steam to trigger when the Steam Overlay is activated * * @param CallbackData - Steam struct containing state of the Steam Overlay */ void FOnlineAsyncTaskManagerSteam::OnExternalUITriggered(GameOverlayActivated_t* CallbackData) { FOnlineAsyncEventSteamExternalUITriggered* NewEvent = new FOnlineAsyncEventSteamExternalUITriggered(SteamSubsystem, (CallbackData->m_bActive != 0) ? true : false); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } /** * Notification event from Steam that server session connection has changed state */ class FOnlineAsyncEventSteamServerConnectionState : public FOnlineAsyncEvent { /** Connection state change */ const EOnlineServerConnectionStatus::Type ConnectionState; /** Hidden on purpose */ FOnlineAsyncEventSteamServerConnectionState() : ConnectionState(EOnlineServerConnectionStatus::NotConnected) { } public: FOnlineAsyncEventSteamServerConnectionState(FOnlineSubsystemSteam* InSubsystem, EOnlineServerConnectionStatus::Type InConnectionState) : FOnlineAsyncEvent(InSubsystem), ConnectionState(InConnectionState) { } virtual ~FOnlineAsyncEventSteamServerConnectionState() { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamServerConnectionState StateChange: %s"), EOnlineServerConnectionStatus::ToString(ConnectionState)); } /** * Give the async task a chance to marshal its data back to the game thread * Can only be called on the game thread by the async task manager */ virtual void Finalize() override { Subsystem->TriggerOnConnectionStatusChangedDelegates(Subsystem->GetSubsystemName().ToString(), EOnlineServerConnectionStatus::Normal, ConnectionState); } }; /** * Client API version of the connected to Steam callback (only called in case of a Steam backend disconnect and then reconnect) */ void FOnlineAsyncTaskManagerSteam::OnSteamServersConnected(SteamServersConnected_t* CallbackData) { FOnlineAsyncEventSteamServerConnectionState* NewEvent = new FOnlineAsyncEventSteamServerConnectionState(SteamSubsystem, EOnlineServerConnectionStatus::Connected); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } /** * Client API version of the disconnected to Steam callback */ void FOnlineAsyncTaskManagerSteam::OnSteamServersDisconnected(SteamServersDisconnected_t* CallbackData) { FOnlineAsyncEventSteamServerConnectionState* NewEvent = new FOnlineAsyncEventSteamServerConnectionState(SteamSubsystem, SteamConnectionResult(CallbackData->m_eResult)); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } /** * Notification event from Steam that server session has been disconnected with the server list */ class FOnlineAsyncEventSteamServerDisconnectedGS : public FOnlineAsyncEvent { private: /** Callback data */ SteamServersDisconnected_t CallbackResults; /** Hidden on purpose */ FOnlineAsyncEventSteamServerDisconnectedGS() { } public: FOnlineAsyncEventSteamServerDisconnectedGS(FOnlineSubsystemSteam* InSubsystem, SteamServersDisconnected_t& InResults) : FOnlineAsyncEvent(InSubsystem), CallbackResults(InResults) { } virtual ~FOnlineAsyncEventSteamServerDisconnectedGS() { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamServerDisconnectedGS Result: %s"), *SteamResultString(CallbackResults.m_eResult)); } /** * Give the async task a chance to marshal its data back to the game thread * Can only be called on the game thread by the async task manager */ virtual void Finalize() override { bool bTriggerConnectionStatusUpdate = true; FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr(Subsystem->GetSessionInterface()); if (SessionInt.IsValid()) { SessionInt->bSteamworksGameServerConnected = false; SessionInt->GameServerSteamId = NULL; // Don't trigger the delegates if a DestroySession() call was made FNamedOnlineSession* Session = SessionInt->GetGameServerSession(); if (Session && Session->SessionState == EOnlineSessionState::Destroying) { bTriggerConnectionStatusUpdate = false; } } if (bTriggerConnectionStatusUpdate) { EOnlineServerConnectionStatus::Type ConnectionState = SteamConnectionResult(CallbackResults.m_eResult); Subsystem->TriggerOnConnectionStatusChangedDelegates(Subsystem->GetSubsystemName().ToString(), EOnlineServerConnectionStatus::Normal, ConnectionState); } } }; /** * GameServer API version of disconnected from Steam backend callback */ void FOnlineAsyncTaskManagerSteam::OnSteamServersDisconnectedGS(SteamServersDisconnected_t* CallbackData) { FOnlineAsyncEventSteamServerDisconnectedGS* NewEvent = new FOnlineAsyncEventSteamServerDisconnectedGS(SteamSubsystem, *CallbackData); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } /** * Notification event from Steam that server login has failed. */ class FOnlineAsyncEventSteamServerFailedGS : public FOnlineAsyncEvent { private: /** Callback data */ SteamServerConnectFailure_t CallbackResults; /** Hidden on purpose */ FOnlineAsyncEventSteamServerFailedGS() { } public: FOnlineAsyncEventSteamServerFailedGS(FOnlineSubsystemSteam* InSubsystem, SteamServerConnectFailure_t& InResults) : FOnlineAsyncEvent(InSubsystem), CallbackResults(InResults) { } virtual ~FOnlineAsyncEventSteamServerFailedGS() { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamServerFailedGS Result: %s"), *SteamResultString(CallbackResults.m_eResult)); } /** * Give the async task a chance to marshal its data back to the game thread * Can only be called on the game thread by the async task manager */ virtual void Finalize() override { if (Subsystem) { Subsystem->TriggerOnSteamServerLoginCompletedDelegates(false); } } }; /** * GameServer API version of disconnected from Steam backend callback */ void FOnlineAsyncTaskManagerSteam::OnSteamServersConnectFailureGS(SteamServerConnectFailure_t* CallbackData) { // Only do something if we are no longer retrying to connect with the backend. if (!CallbackData->m_bStillRetrying) { FOnlineAsyncEventSteamServerFailedGS* NewEvent = new FOnlineAsyncEventSteamServerFailedGS(SteamSubsystem, *CallbackData); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } } /** * Notification event from Steam that server session has been secured on the backend */ class FOnlineAsyncEventSteamServerPolicyResponseGS : public FOnlineAsyncEvent { private: /** Callback data */ GSPolicyResponse_t CallbackResults; /** Hidden on purpose */ FOnlineAsyncEventSteamServerPolicyResponseGS() { } public: FOnlineAsyncEventSteamServerPolicyResponseGS(FOnlineSubsystemSteam* InSubsystem, GSPolicyResponse_t& InResults) : FOnlineAsyncEvent(InSubsystem), CallbackResults(InResults) { } virtual ~FOnlineAsyncEventSteamServerPolicyResponseGS() { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamServerPolicyResponseGS Secure: %d"), CallbackResults.m_bSecure); } /** * Give the async task a chance to marshal its data back to the game thread * Can only be called on the game thread by the async task manager */ virtual void Finalize() override { FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr(Subsystem->GetSessionInterface()); if (SessionInt.IsValid()) { SessionInt->bPolicyResponseReceived = true; if (!SessionInt->bSteamworksGameServerConnected || !SessionInt->GameServerSteamId->IsValid()) { UE_LOG_ONLINE(Warning, TEXT("Unexpected GSPolicyResponse callback")); } } } }; /** * Notification event from Steam that server session has been secured */ void FOnlineAsyncTaskManagerSteam::OnPolicyResponseGS(GSPolicyResponse_t* CallbackData) { FOnlineAsyncEventSteamServerPolicyResponseGS* NewEvent = new FOnlineAsyncEventSteamServerPolicyResponseGS(SteamSubsystem, *CallbackData); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } class FOnlineAsyncEventSteamGetTicketForWebApiResponse : public FOnlineAsyncEvent { private: GetTicketForWebApiResponse_t CallbackResults; public: FOnlineAsyncEventSteamGetTicketForWebApiResponse(FOnlineSubsystemSteam* InSubsystem, const GetTicketForWebApiResponse_t& InResults) : FOnlineAsyncEvent(InSubsystem) , CallbackResults(InResults) { } virtual ~FOnlineAsyncEventSteamGetTicketForWebApiResponse() { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamGetTicketForWebApiResponse Received code %d."), (int32)CallbackResults.m_eResult); } /** * Give the async task a chance to marshal its data back to the game thread * Can only be called on the game thread by the async task manager */ virtual void Finalize() override { FOnlineAuthSteamPtr AuthInt = StaticCastSharedPtr(Subsystem->GetAuthInterface()); if (AuthInt.IsValid()) { FString ResultToken = BytesToHex(CallbackResults.m_rgubTicket, CallbackResults.m_cubTicket); AuthInt->OnGetTicketForWebResponse(CallbackResults.m_hAuthTicket, ResultToken); } else { UE_LOG_ONLINE(Warning, TEXT("Auth interface is not valid!")); } } }; void FOnlineAsyncTaskManagerSteam::OnGetTicketForWebApiResponse(GetTicketForWebApiResponse_t* CallbackData) { FOnlineAsyncEventSteamGetTicketForWebApiResponse* NewEvent = new FOnlineAsyncEventSteamGetTicketForWebApiResponse(SteamSubsystem, *CallbackData); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } class FOnlineAsyncEventSteamAuthenticationResponse : public FOnlineAsyncEvent { private: ValidateAuthTicketResponse_t CallbackResults; bool bIsServer; FOnlineAsyncEventSteamAuthenticationResponse() { } public: FOnlineAsyncEventSteamAuthenticationResponse(FOnlineSubsystemSteam* InSubsystem, const ValidateAuthTicketResponse_t& InResults, bool bInServerCall) : FOnlineAsyncEvent(InSubsystem), CallbackResults(InResults), bIsServer(bInServerCall) { } virtual ~FOnlineAsyncEventSteamAuthenticationResponse() { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamAuthenticationResponse Received code %d. Is server? %d"), (int32)CallbackResults.m_eAuthSessionResponse, bIsServer); } /** * Give the async task a chance to marshal its data back to the game thread * Can only be called on the game thread by the async task manager */ virtual void Finalize() override { FOnlineAuthSteamPtr AuthInt = StaticCastSharedPtr(Subsystem->GetAuthInterface()); if (AuthInt.IsValid()) { AuthInt->OnAuthResult(*FUniqueNetIdSteam::Create(CallbackResults.m_SteamID), CallbackResults.m_eAuthSessionResponse); } else { UE_LOG_ONLINE(Warning, TEXT("Auth interface is not valid!")); } } }; /** * Notification event from Steam regarding a ticket authentication response for server */ void FOnlineAsyncTaskManagerSteam::OnAuthenticationResponseGS(ValidateAuthTicketResponse_t* CallbackData) { FOnlineAsyncEventSteamAuthenticationResponse* NewEvent = new FOnlineAsyncEventSteamAuthenticationResponse(SteamSubsystem, *CallbackData, true); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } /** * Notification event from Steam regarding a ticket authentication response for clients */ void FOnlineAsyncTaskManagerSteam::OnAuthenticationResponse(ValidateAuthTicketResponse_t* CallbackData) { FOnlineAsyncEventSteamAuthenticationResponse* NewEvent = new FOnlineAsyncEventSteamAuthenticationResponse(SteamSubsystem, *CallbackData, false); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } /** * Notification event from Steam that a P2P connection has failed */ class FOnlineAsyncEventSteamShutdown : public FOnlineAsyncEvent { FOnlineAsyncEventSteamShutdown() : FOnlineAsyncEvent(NULL) { } public: FOnlineAsyncEventSteamShutdown(FOnlineSubsystemSteam* InSubsystem) : FOnlineAsyncEvent(InSubsystem) { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamShutdown shutdown received.")); } /** * Give the async task a chance to marshal its data back to the game thread * Can only be called on the game thread by the async task manager */ virtual void Finalize() override { FPlatformMisc::RequestExit(false); } }; /** * Delegate registered with Steam to trigger when Steam is shutting down * * @param CallbackData - Steam struct containing shutdown information */ void FOnlineAsyncTaskManagerSteam::OnSteamShutdown(SteamShutdown_t* CallbackData) { FOnlineAsyncEventSteamShutdown* NewEvent = new FOnlineAsyncEventSteamShutdown(SteamSubsystem); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } /** * Notification event from Steam that rich presence has updated */ class FOnlineAsyncEventSteamRichPresenceUpdate : public FOnlineAsyncEvent { FOnlineAsyncEventSteamRichPresenceUpdate() = delete; FUniqueNetIdSteamRef TargetSteamId; public: FOnlineAsyncEventSteamRichPresenceUpdate(FOnlineSubsystemSteam* InSubsystem, CSteamID InSteamId) : FOnlineAsyncEvent(InSubsystem), TargetSteamId(FUniqueNetIdSteam::Create(InSteamId)) { } FOnlineAsyncEventSteamRichPresenceUpdate(FOnlineSubsystemSteam* InSubsystem, uint64 InSteamId) : FOnlineAsyncEvent(InSubsystem), TargetSteamId(FUniqueNetIdSteam::Create(InSteamId)) { } /** * Get a human readable description of task */ virtual FString ToString() const override { return FString::Printf(TEXT("FOnlineAsyncEventSteamRichPresenceUpdate got new information about user %s"), *TargetSteamId->ToString()); } /** * Give the async task a chance to marshal its data back to the game thread * Can only be called on the game thread by the async task manager */ virtual void Finalize() override { FOnlinePresenceSteamPtr PresenceInterface = StaticCastSharedPtr(Subsystem->GetPresenceInterface()); if (PresenceInterface.IsValid()) { PresenceInterface->UpdatePresenceForUser(*TargetSteamId); } } }; /** * Delegate registered with Steam to trigger when Steam gets updates about user rich presence * * @param CallbackData - Steam struct containing user that got their data updated */ void FOnlineAsyncTaskManagerSteam::OnRichPresenceUpdate(FriendRichPresenceUpdate_t* CallbackData) { FOnlineAsyncEventSteamRichPresenceUpdate* NewEvent = new FOnlineAsyncEventSteamRichPresenceUpdate(SteamSubsystem, CallbackData->m_steamIDFriend); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } /** * Delegate registered with Steam to trigger when Steam gets updates about user rich presence * * @param CallbackData - Steam struct containing user that got their data updated */ void FOnlineAsyncTaskManagerSteam::OnFriendStatusUpdate(PersonaStateChange_t* CallbackData) { int ChangedData = CallbackData->m_nChangeFlags; // Licensees can feel free to expand on this by adding their own watch events as well. int RichPresenceWatchedEvents = (k_EPersonaChangeGameServer | k_EPersonaChangeGamePlayed | k_EPersonaChangeStatus | k_EPersonaChangeGoneOffline | k_EPersonaChangeComeOnline); if (ChangedData & RichPresenceWatchedEvents) { FOnlineAsyncEventSteamRichPresenceUpdate* NewEvent = new FOnlineAsyncEventSteamRichPresenceUpdate(SteamSubsystem, CallbackData->m_ulSteamID); UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString()); AddToOutQueue(NewEvent); } }