mirror of
https://forge.banditvault.co.uk/racoon/MinecraftConsoles.git
synced 2026-05-22 00:05:11 +00:00
feat: Implement game invites functionality and UI integration for LceLive
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#ifdef _WINDOWS64
|
||||
#include "../../Windows64/Network/WinsockNetLayer.h"
|
||||
#include "../../Windows64/Windows64_Xuid.h"
|
||||
#include "../../Windows64/Windows64_LceLive.h"
|
||||
#include "../../Minecraft.h"
|
||||
#include "../../User.h"
|
||||
#include "../../MinecraftServer.h"
|
||||
@@ -422,6 +423,7 @@ bool CPlatformNetworkManagerStub::LeaveGame(bool bMigrateHost)
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
WinsockNetLayer::StopAdvertising();
|
||||
Win64LceLive::DeactivateGameInvitesSync();
|
||||
#endif
|
||||
|
||||
// If we are the host wait for the game server to end
|
||||
@@ -1039,7 +1041,37 @@ bool CPlatformNetworkManagerStub::IsHost()
|
||||
|
||||
bool CPlatformNetworkManagerStub::JoinGameFromInviteInfo( int userIndex, int userMask, const INVITE_INFO *pInviteInfo)
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
if (pInviteInfo == nullptr || !pInviteInfo->sessionActive || pInviteInfo->hostPort <= 0 || pInviteInfo->hostIP[0] == 0)
|
||||
return false;
|
||||
|
||||
m_bLeavingGame = false;
|
||||
m_bLeaveGameOnTick = false;
|
||||
IQNet::s_isHosting = false;
|
||||
m_pIQNet->ClientJoinGame();
|
||||
|
||||
IQNet::m_player[0].m_smallId = 0;
|
||||
IQNet::m_player[0].m_isRemote = true;
|
||||
IQNet::m_player[0].m_isHostPlayer = true;
|
||||
IQNet::m_player[0].m_resolvedXuid = Win64Xuid::GetLegacyEmbeddedHostXuid();
|
||||
wcsncpy_s(IQNet::m_player[0].m_gamertag, 32, pInviteInfo->hostName, _TRUNCATE);
|
||||
|
||||
WinsockNetLayer::StopDiscovery();
|
||||
|
||||
wcsncpy_s(m_joinHostName, 32, pInviteInfo->hostName, _TRUNCATE);
|
||||
m_joinLocalUsersMask = userMask;
|
||||
|
||||
if (!WinsockNetLayer::BeginJoinGame(pInviteInfo->hostIP, pInviteInfo->hostPort))
|
||||
{
|
||||
app.DebugPrintf("Win64 invite: Failed to connect to %s:%d\n", pInviteInfo->hostIP, pInviteInfo->hostPort);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_bJoinPending = true;
|
||||
return true;
|
||||
#else
|
||||
return ( m_pIQNet->JoinGameFromInviteInfo( userIndex, userMask, pInviteInfo ) == S_OK);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CPlatformNetworkManagerStub::SetSessionTexturePackParentId( int id )
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "UI.h"
|
||||
#include "UIScene_LceLiveInvites.h"
|
||||
#include "../../Minecraft.h"
|
||||
#include "../../Windows64/Network/WinsockNetLayer.h"
|
||||
|
||||
// Fallbacks until string ID headers are regenerated.
|
||||
#ifndef IDS_TITLE_SEND_INVITE
|
||||
@@ -212,6 +213,7 @@ void UIScene_LceLiveInvites::FetchAndDisplay()
|
||||
if (accessToken.empty())
|
||||
{
|
||||
m_friends.clear();
|
||||
m_invitedAccountIds.clear();
|
||||
m_bDataReady = true;
|
||||
m_statusMessage = L"Sign in to LCELIVE to invite friends.";
|
||||
RebuildLists();
|
||||
@@ -230,6 +232,16 @@ void UIScene_LceLiveInvites::FetchAndDisplay()
|
||||
}
|
||||
|
||||
m_friends = result.friends;
|
||||
m_invitedAccountIds.clear();
|
||||
const Win64LceLive::GameInvitesResult inviteState = Win64LceLive::GetGameInvitesSync();
|
||||
if (inviteState.success)
|
||||
{
|
||||
for (const Win64LceLive::GameInviteEntry &invite : inviteState.outgoing)
|
||||
{
|
||||
if (invite.status == "pending")
|
||||
m_invitedAccountIds.push_back(invite.recipientAccountId);
|
||||
}
|
||||
}
|
||||
m_bDataReady = true;
|
||||
|
||||
if (m_friends.empty())
|
||||
@@ -259,7 +271,12 @@ void UIScene_LceLiveInvites::RebuildLists()
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
for (const Win64LceLive::SocialEntry &entry : m_friends)
|
||||
m_friendsList.addItem(BuildFriendLabel(entry));
|
||||
{
|
||||
std::wstring label = BuildFriendLabel(entry);
|
||||
if (AlreadyInvited(entry.accountId))
|
||||
label += L" [SENT]";
|
||||
m_friendsList.addItem(label);
|
||||
}
|
||||
#endif
|
||||
|
||||
UpdateStatusLabel();
|
||||
@@ -332,14 +349,6 @@ void UIScene_LceLiveInvites::PromptInviteFriendAtIndex(int friendIndex)
|
||||
m_pendingInviteAccountId = m_friends[friendIndex].accountId;
|
||||
m_pendingInviteLabel = BuildFriendLabel(m_friends[friendIndex]);
|
||||
|
||||
if (AlreadyInvited(m_pendingInviteAccountId))
|
||||
{
|
||||
m_statusMessage = L"Already invited: ";
|
||||
m_statusMessage += m_pendingInviteLabel;
|
||||
UpdateStatusLabel();
|
||||
return;
|
||||
}
|
||||
|
||||
UINT optionIds[2];
|
||||
optionIds[0] = IDS_NO;
|
||||
optionIds[1] = IDS_YES;
|
||||
@@ -359,6 +368,29 @@ void UIScene_LceLiveInvites::InvitePendingFriend()
|
||||
if (m_pendingInviteAccountId.empty())
|
||||
return;
|
||||
|
||||
if (!g_NetworkManager.IsHost() || !WinsockNetLayer::IsHosting() || g_Win64MultiplayerIP[0] == 0 || g_Win64MultiplayerPort <= 0)
|
||||
{
|
||||
m_statusMessage = L"The game session is no longer active.";
|
||||
m_pendingInviteAccountId.clear();
|
||||
m_pendingInviteLabel.clear();
|
||||
UpdateStatusLabel();
|
||||
return;
|
||||
}
|
||||
|
||||
const Win64LceLive::SocialActionResult result = Win64LceLive::SendGameInviteSync(
|
||||
m_pendingInviteAccountId,
|
||||
g_Win64MultiplayerIP,
|
||||
g_Win64MultiplayerPort,
|
||||
"");
|
||||
if (!result.success)
|
||||
{
|
||||
m_statusMessage = Utf8ToWideLocal(result.error);
|
||||
m_pendingInviteAccountId.clear();
|
||||
m_pendingInviteLabel.clear();
|
||||
UpdateStatusLabel();
|
||||
return;
|
||||
}
|
||||
|
||||
m_invitedAccountIds.push_back(m_pendingInviteAccountId);
|
||||
|
||||
m_statusMessage = L"Invite sent to ";
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
#ifndef IDS_TEXT_ACCEPT_REQUEST_CONFIRMATION
|
||||
#define IDS_TEXT_ACCEPT_REQUEST_CONFIRMATION IDS_CONFIRM_EXIT_GAME
|
||||
#endif
|
||||
#ifndef IDS_TITLE_SEND_INVITE
|
||||
#define IDS_TITLE_SEND_INVITE IDS_TITLE_FRIEND_REQUEST
|
||||
#endif
|
||||
#ifndef IDS_TEXT_SEND_INVITE_CONFIRMATION
|
||||
#define IDS_TEXT_SEND_INVITE_CONFIRMATION IDS_TEXT_ACCEPT_REQUEST_CONFIRMATION
|
||||
#endif
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
namespace
|
||||
@@ -30,24 +36,37 @@ namespace
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring BuildRequestLabel(bool incoming, const std::string &username, const std::string &displayName)
|
||||
std::wstring BuildInviteLabel(
|
||||
const std::string &senderDisplayName,
|
||||
const std::string &senderUsername,
|
||||
const std::string &hostName,
|
||||
bool sessionActive)
|
||||
{
|
||||
std::wstring line = incoming ? L"[IN] " : L"[OUT] ";
|
||||
std::wstring line = L"[INVITE] ";
|
||||
|
||||
if (!displayName.empty())
|
||||
line += Utf8ToWideLocal(displayName);
|
||||
else if (!username.empty())
|
||||
line += Utf8ToWideLocal(username);
|
||||
if (!senderDisplayName.empty())
|
||||
line += Utf8ToWideLocal(senderDisplayName);
|
||||
else if (!senderUsername.empty())
|
||||
line += Utf8ToWideLocal(senderUsername);
|
||||
else
|
||||
line += L"<unknown>";
|
||||
|
||||
if (!username.empty())
|
||||
if (!senderUsername.empty())
|
||||
{
|
||||
line += L" (@";
|
||||
line += Utf8ToWideLocal(username);
|
||||
line += Utf8ToWideLocal(senderUsername);
|
||||
line += L")";
|
||||
}
|
||||
|
||||
if (!hostName.empty())
|
||||
{
|
||||
line += L" - ";
|
||||
line += Utf8ToWideLocal(hostName);
|
||||
}
|
||||
|
||||
if (!sessionActive)
|
||||
line += L" [inactive]";
|
||||
|
||||
return line;
|
||||
}
|
||||
}
|
||||
@@ -62,7 +81,7 @@ UIScene_LceLiveRequests::UIScene_LceLiveRequests(int iPad, void *initData, UILay
|
||||
|
||||
m_requestsList.init(eControl_RequestsList);
|
||||
m_actionsList.init(eControl_ActionsList);
|
||||
m_labelRequestsTitle.init(L"REQUESTS");
|
||||
m_labelRequestsTitle.init(L"GAME INVITES");
|
||||
m_labelActionsTitle.init(L"ACTIONS");
|
||||
m_labelStatus.init(L"");
|
||||
m_controlRequestsTimer.setVisible(false);
|
||||
@@ -75,8 +94,10 @@ UIScene_LceLiveRequests::UIScene_LceLiveRequests(int iPad, void *initData, UILay
|
||||
m_statusMessage.clear();
|
||||
m_bDataReady = false;
|
||||
#ifdef _WINDOWS64
|
||||
m_pendingRequestAccountId.clear();
|
||||
m_pendingRequestIncoming = false;
|
||||
m_pendingInviteId.clear();
|
||||
m_pendingInviteHostIp.clear();
|
||||
m_pendingInviteHostPort = 0;
|
||||
m_pendingInviteHostName.clear();
|
||||
#endif
|
||||
|
||||
doHorizontalResizeCheck();
|
||||
@@ -199,13 +220,13 @@ void UIScene_LceLiveRequests::FetchAndDisplay()
|
||||
{
|
||||
m_entries.clear();
|
||||
m_bDataReady = true;
|
||||
m_statusMessage = L"Sign in to view and manage friend requests.";
|
||||
m_statusMessage = L"Sign in to view and manage game invites.";
|
||||
RebuildLists();
|
||||
return;
|
||||
}
|
||||
|
||||
const int previousSelection = m_requestsList.getCurrentSelection();
|
||||
const Win64LceLive::PendingRequestsResult result = Win64LceLive::GetPendingRequestsSync();
|
||||
const Win64LceLive::GameInvitesResult result = Win64LceLive::GetGameInvitesSync();
|
||||
if (!result.success)
|
||||
{
|
||||
m_entries.clear();
|
||||
@@ -216,29 +237,28 @@ void UIScene_LceLiveRequests::FetchAndDisplay()
|
||||
}
|
||||
|
||||
m_entries.clear();
|
||||
for (const Win64LceLive::SocialEntry &entry : result.incoming)
|
||||
for (const Win64LceLive::GameInviteEntry &entry : result.incoming)
|
||||
{
|
||||
RequestEntry row = {};
|
||||
row.incoming = true;
|
||||
row.accountId = entry.accountId;
|
||||
row.username = entry.username;
|
||||
row.displayName = entry.displayName;
|
||||
m_entries.push_back(row);
|
||||
}
|
||||
for (const Win64LceLive::SocialEntry &entry : result.outgoing)
|
||||
{
|
||||
RequestEntry row = {};
|
||||
row.incoming = false;
|
||||
row.accountId = entry.accountId;
|
||||
row.username = entry.username;
|
||||
row.displayName = entry.displayName;
|
||||
row.inviteId = entry.inviteId;
|
||||
row.senderAccountId = entry.senderAccountId;
|
||||
row.senderUsername = entry.senderUsername;
|
||||
row.senderDisplayName = entry.senderDisplayName;
|
||||
row.recipientAccountId = entry.recipientAccountId;
|
||||
row.recipientUsername = entry.recipientUsername;
|
||||
row.recipientDisplayName = entry.recipientDisplayName;
|
||||
row.hostIp = entry.hostIp;
|
||||
row.hostPort = entry.hostPort;
|
||||
row.hostName = entry.hostName;
|
||||
row.status = entry.status;
|
||||
row.sessionActive = entry.sessionActive;
|
||||
m_entries.push_back(row);
|
||||
}
|
||||
|
||||
m_bDataReady = true;
|
||||
if (m_entries.empty() && m_statusMessage.empty())
|
||||
m_statusMessage = L"No pending requests.";
|
||||
else if (!m_entries.empty() && m_statusMessage == L"No pending requests.")
|
||||
m_statusMessage = L"No pending game invites.";
|
||||
else if (!m_entries.empty() && m_statusMessage == L"No pending game invites.")
|
||||
m_statusMessage.clear();
|
||||
|
||||
RebuildLists();
|
||||
@@ -255,7 +275,7 @@ void UIScene_LceLiveRequests::FetchAndDisplay()
|
||||
#else
|
||||
m_entries.clear();
|
||||
m_bDataReady = true;
|
||||
m_statusMessage = L"Requests are only available on Windows64 builds.";
|
||||
m_statusMessage = L"Game invites are only available on Windows64 builds.";
|
||||
RebuildLists();
|
||||
#endif
|
||||
}
|
||||
@@ -266,7 +286,7 @@ void UIScene_LceLiveRequests::RebuildLists()
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
for (const RequestEntry &entry : m_entries)
|
||||
m_requestsList.addItem(BuildRequestLabel(entry.incoming, entry.username, entry.displayName));
|
||||
m_requestsList.addItem(BuildInviteLabel(entry.senderDisplayName, entry.senderUsername, entry.hostName, entry.sessionActive));
|
||||
#endif
|
||||
|
||||
UpdateStatusLabel();
|
||||
@@ -284,7 +304,7 @@ void UIScene_LceLiveRequests::UpdateStatusLabel()
|
||||
m_labelStatus.setVisible(true);
|
||||
}
|
||||
|
||||
m_labelRequestsTitle.setLabel(L"REQUESTS", true, true);
|
||||
m_labelRequestsTitle.setLabel(L"GAME INVITES", true, true);
|
||||
m_labelActionsTitle.setLabel(L"ACTIONS", true, true);
|
||||
}
|
||||
|
||||
@@ -315,29 +335,24 @@ void UIScene_LceLiveRequests::PromptResolveSelectedRequest()
|
||||
const int selectedIndex = FocusedRequestIndex();
|
||||
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(m_entries.size()))
|
||||
{
|
||||
m_statusMessage = L"Select a request first.";
|
||||
UpdateStatusLabel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_entries[selectedIndex].incoming)
|
||||
{
|
||||
m_statusMessage = L"Outgoing requests are waiting on the other player.";
|
||||
m_statusMessage = L"Select an invite first.";
|
||||
UpdateStatusLabel();
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
m_pendingRequestAccountId = m_entries[selectedIndex].accountId;
|
||||
m_pendingRequestIncoming = true;
|
||||
m_pendingInviteId = m_entries[selectedIndex].inviteId;
|
||||
m_pendingInviteHostIp = m_entries[selectedIndex].hostIp;
|
||||
m_pendingInviteHostPort = m_entries[selectedIndex].hostPort;
|
||||
m_pendingInviteHostName = m_entries[selectedIndex].hostName;
|
||||
|
||||
UINT optionIds[2];
|
||||
optionIds[0] = IDS_NO;
|
||||
optionIds[1] = IDS_YES;
|
||||
|
||||
ui.RequestAlertMessage(
|
||||
IDS_TITLE_FRIEND_REQUEST,
|
||||
IDS_TEXT_ACCEPT_REQUEST_CONFIRMATION,
|
||||
IDS_TITLE_SEND_INVITE,
|
||||
IDS_TEXT_SEND_INVITE_CONFIRMATION,
|
||||
optionIds,
|
||||
2,
|
||||
m_iPad,
|
||||
@@ -352,26 +367,11 @@ void UIScene_LceLiveRequests::PerformAccept()
|
||||
const int selectedIndex = FocusedRequestIndex();
|
||||
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(m_entries.size()))
|
||||
{
|
||||
m_statusMessage = L"Select a request first.";
|
||||
m_statusMessage = L"Select an invite first.";
|
||||
UpdateStatusLabel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_entries[selectedIndex].incoming)
|
||||
{
|
||||
m_statusMessage = L"Only incoming requests can be accepted.";
|
||||
UpdateStatusLabel();
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string accountId = m_entries[selectedIndex].accountId;
|
||||
const Win64LceLive::SocialActionResult result = Win64LceLive::AcceptFriendRequestSync(accountId);
|
||||
if (result.success)
|
||||
m_statusMessage = L"Friend request accepted.";
|
||||
else
|
||||
m_statusMessage = Utf8ToWideLocal(result.error);
|
||||
|
||||
FetchAndDisplay();
|
||||
ResolvePendingRequest(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -381,48 +381,95 @@ void UIScene_LceLiveRequests::PerformDecline()
|
||||
const int selectedIndex = FocusedRequestIndex();
|
||||
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(m_entries.size()))
|
||||
{
|
||||
m_statusMessage = L"Select a request first.";
|
||||
m_statusMessage = L"Select an invite first.";
|
||||
UpdateStatusLabel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_entries[selectedIndex].incoming)
|
||||
{
|
||||
m_statusMessage = L"Only incoming requests can be declined.";
|
||||
UpdateStatusLabel();
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string accountId = m_entries[selectedIndex].accountId;
|
||||
const Win64LceLive::SocialActionResult result = Win64LceLive::DeclineFriendRequestSync(accountId);
|
||||
if (result.success)
|
||||
m_statusMessage = L"Friend request declined.";
|
||||
else
|
||||
m_statusMessage = Utf8ToWideLocal(result.error);
|
||||
|
||||
FetchAndDisplay();
|
||||
ResolvePendingRequest(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
void UIScene_LceLiveRequests::ResolvePendingRequest(bool accept)
|
||||
{
|
||||
if (!m_pendingRequestIncoming || m_pendingRequestAccountId.empty())
|
||||
if (m_pendingInviteId.empty())
|
||||
return;
|
||||
|
||||
const std::string accountId = m_pendingRequestAccountId;
|
||||
m_pendingRequestAccountId.clear();
|
||||
m_pendingRequestIncoming = false;
|
||||
const std::string inviteId = m_pendingInviteId;
|
||||
|
||||
const Win64LceLive::SocialActionResult result = accept
|
||||
? Win64LceLive::AcceptFriendRequestSync(accountId)
|
||||
: Win64LceLive::DeclineFriendRequestSync(accountId);
|
||||
if (accept)
|
||||
{
|
||||
const Win64LceLive::GameInviteActionResult result = Win64LceLive::AcceptGameInviteSync(inviteId);
|
||||
if (result.success)
|
||||
{
|
||||
m_pendingInviteHostIp = result.hostIp;
|
||||
m_pendingInviteHostPort = result.hostPort;
|
||||
m_pendingInviteHostName = result.hostName;
|
||||
JoinAcceptedInvite();
|
||||
m_pendingInviteId.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.success)
|
||||
m_statusMessage = accept ? L"Friend request accepted." : L"Friend request declined.";
|
||||
else
|
||||
m_statusMessage = Utf8ToWideLocal(result.error);
|
||||
UpdateStatusLabel();
|
||||
FetchAndDisplay();
|
||||
m_pendingInviteId.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const Win64LceLive::SocialActionResult declineResult = Win64LceLive::DeclineGameInviteSync(inviteId);
|
||||
if (declineResult.success)
|
||||
m_statusMessage = L"Game invite declined.";
|
||||
else
|
||||
m_statusMessage = Utf8ToWideLocal(declineResult.error);
|
||||
m_pendingInviteId.clear();
|
||||
|
||||
FetchAndDisplay();
|
||||
}
|
||||
|
||||
void UIScene_LceLiveRequests::JoinAcceptedInvite()
|
||||
{
|
||||
if (m_pendingInviteHostIp.empty() || m_pendingInviteHostPort <= 0)
|
||||
{
|
||||
m_statusMessage = L"The game session is no longer active.";
|
||||
UpdateStatusLabel();
|
||||
FetchAndDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
ProfileManager.SetLockedProfile(m_iPad);
|
||||
ProfileManager.SetPrimaryPad(m_iPad);
|
||||
g_NetworkManager.SetLocalGame(false);
|
||||
ProfileManager.QuerySigninStatus();
|
||||
Minecraft::GetInstance()->clearConnectionFailed();
|
||||
|
||||
int localUsersMask = 0;
|
||||
for (unsigned int index = 0; index < XUSER_MAX_COUNT; ++index)
|
||||
{
|
||||
if (ProfileManager.IsSignedIn(index))
|
||||
localUsersMask |= g_NetworkManager.GetLocalPlayerMask(index);
|
||||
}
|
||||
|
||||
INVITE_INFO inviteInfo = {};
|
||||
inviteInfo.netVersion = MINECRAFT_NET_VERSION;
|
||||
strcpy_s(inviteInfo.hostIP, m_pendingInviteHostIp.c_str());
|
||||
inviteInfo.hostPort = m_pendingInviteHostPort;
|
||||
inviteInfo.sessionActive = true;
|
||||
const std::wstring hostNameWide = m_pendingInviteHostName.empty() ? L"LCELive" : Utf8ToWideLocal(m_pendingInviteHostName);
|
||||
wcsncpy_s(inviteInfo.hostName, hostNameWide.c_str(), _TRUNCATE);
|
||||
strcpy_s(inviteInfo.inviteId, m_pendingInviteId.c_str());
|
||||
|
||||
const bool success = g_NetworkManager.JoinGameFromInviteInfo(m_iPad, localUsersMask, &inviteInfo);
|
||||
if (!success)
|
||||
m_statusMessage = L"Could not join this game.";
|
||||
else
|
||||
m_statusMessage = L"Joining game...";
|
||||
|
||||
m_pendingInviteId.clear();
|
||||
m_pendingInviteHostIp.clear();
|
||||
m_pendingInviteHostPort = 0;
|
||||
m_pendingInviteHostName.clear();
|
||||
UpdateStatusLabel();
|
||||
FetchAndDisplay();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "../../Windows64/Windows64_LceLive.h"
|
||||
#endif
|
||||
|
||||
// LceLive sub-scene: friend requests list.
|
||||
// LceLive sub-scene: game invite inbox.
|
||||
// Uses the native Start Game style (LoadOrJoinMenu): left list of requests,
|
||||
// right list of actions.
|
||||
|
||||
@@ -29,10 +29,18 @@ private:
|
||||
|
||||
struct RequestEntry
|
||||
{
|
||||
bool incoming;
|
||||
std::string accountId;
|
||||
std::string username;
|
||||
std::string displayName;
|
||||
std::string inviteId;
|
||||
std::string senderAccountId;
|
||||
std::string senderUsername;
|
||||
std::string senderDisplayName;
|
||||
std::string recipientAccountId;
|
||||
std::string recipientUsername;
|
||||
std::string recipientDisplayName;
|
||||
std::string hostIp;
|
||||
int hostPort;
|
||||
std::string hostName;
|
||||
std::string status;
|
||||
bool sessionActive;
|
||||
};
|
||||
|
||||
UIControl_ButtonList m_requestsList;
|
||||
@@ -46,8 +54,10 @@ private:
|
||||
std::wstring m_statusMessage;
|
||||
bool m_bDataReady;
|
||||
#ifdef _WINDOWS64
|
||||
std::string m_pendingRequestAccountId;
|
||||
bool m_pendingRequestIncoming;
|
||||
std::string m_pendingInviteId;
|
||||
std::string m_pendingInviteHostIp;
|
||||
int m_pendingInviteHostPort;
|
||||
std::string m_pendingInviteHostName;
|
||||
#endif
|
||||
UI_BEGIN_MAP_ELEMENTS_AND_NAMES(UIScene)
|
||||
UI_MAP_ELEMENT(m_requestsList, "SavesList")
|
||||
@@ -90,6 +100,7 @@ private:
|
||||
void PerformDecline();
|
||||
#ifdef _WINDOWS64
|
||||
void ResolvePendingRequest(bool accept);
|
||||
void JoinAcceptedInvite();
|
||||
static int ResolveRequestConfirmCallback(void *pParam, int iPad, C4JStorage::EMessageResult result);
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -148,6 +148,22 @@ namespace
|
||||
return it->get<std::string>();
|
||||
}
|
||||
|
||||
int JsonIntOrDefault(const Json &object, const char *key, int defaultValue)
|
||||
{
|
||||
const Json::const_iterator it = object.find(key);
|
||||
if (it == object.end() || !it->is_number_integer())
|
||||
return defaultValue;
|
||||
return it->get<int>();
|
||||
}
|
||||
|
||||
bool JsonBoolOrDefault(const Json &object, const char *key, bool defaultValue)
|
||||
{
|
||||
const Json::const_iterator it = object.find(key);
|
||||
if (it == object.end() || !it->is_boolean())
|
||||
return defaultValue;
|
||||
return it->get<bool>();
|
||||
}
|
||||
|
||||
std::string ParseErrorMessage(const std::string &responseBody, const std::string &fallback);
|
||||
|
||||
void TrimTrailingSlashes(std::string *value)
|
||||
@@ -1401,6 +1417,214 @@ namespace Win64LceLive
|
||||
|
||||
return { true, std::string() };
|
||||
}
|
||||
|
||||
GameInvitesResult GetGameInvitesSync()
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, {}, {}, "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/sessions/invites";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
return { false, {}, {}, ParseErrorMessage(responseBody, "Failed to get game invites.") };
|
||||
|
||||
const Json responseJson = Json::parse(responseBody, nullptr, false);
|
||||
if (!responseJson.is_object())
|
||||
return { false, {}, {}, "Invalid game invites response." };
|
||||
|
||||
auto parseList = [&](const char *key)
|
||||
{
|
||||
std::vector<GameInviteEntry> result;
|
||||
const Json::const_iterator it = responseJson.find(key);
|
||||
if (it != responseJson.end() && it->is_array())
|
||||
{
|
||||
for (const Json &entry : *it)
|
||||
{
|
||||
if (!entry.is_object())
|
||||
continue;
|
||||
|
||||
GameInviteEntry invite = {};
|
||||
invite.inviteId = JsonStringOrEmpty(entry, "inviteId");
|
||||
invite.senderAccountId = JsonStringOrEmpty(entry, "senderAccountId");
|
||||
invite.senderUsername = JsonStringOrEmpty(entry, "senderUsername");
|
||||
invite.senderDisplayName = JsonStringOrEmpty(entry, "senderDisplayName");
|
||||
invite.recipientAccountId = JsonStringOrEmpty(entry, "recipientAccountId");
|
||||
invite.recipientUsername = JsonStringOrEmpty(entry, "recipientUsername");
|
||||
invite.recipientDisplayName = JsonStringOrEmpty(entry, "recipientDisplayName");
|
||||
invite.hostIp = JsonStringOrEmpty(entry, "hostIp");
|
||||
invite.hostPort = JsonIntOrDefault(entry, "hostPort", 0);
|
||||
invite.hostName = JsonStringOrEmpty(entry, "hostName");
|
||||
invite.status = JsonStringOrEmpty(entry, "status");
|
||||
invite.sessionActive = JsonBoolOrDefault(entry, "sessionActive", false);
|
||||
invite.createdUtc = JsonStringOrEmpty(entry, "createdAtUtc");
|
||||
invite.expiresUtc = JsonStringOrEmpty(entry, "expiresAtUtc");
|
||||
result.push_back(invite);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
return { true, parseList("incoming"), parseList("outgoing"), std::string() };
|
||||
}
|
||||
|
||||
SocialActionResult SendGameInviteSync(const std::string &recipientAccountId, const std::string &hostIp, int hostPort, const std::string &hostName)
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
std::string refreshToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
{
|
||||
accessToken = g_state.session.accessToken;
|
||||
refreshToken = g_state.session.refreshToken;
|
||||
}
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, "Not signed in to LCELive." };
|
||||
|
||||
Json bodyJson;
|
||||
bodyJson["recipientAccountId"] = recipientAccountId;
|
||||
bodyJson["hostIp"] = hostIp;
|
||||
bodyJson["hostPort"] = hostPort;
|
||||
bodyJson["hostName"] = hostName;
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/sessions/invites";
|
||||
req.body = bodyJson.dump();
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody))
|
||||
return { false, "Failed to contact LCELive while sending game invite." };
|
||||
|
||||
if (status == 401 && !refreshToken.empty())
|
||||
{
|
||||
std::string refreshError;
|
||||
if (RefreshSessionSync(&refreshError))
|
||||
{
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
responseBody.clear();
|
||||
if (!PerformJsonRequest(req, &status, &responseBody))
|
||||
return { false, "Failed to contact LCELive while sending game invite." };
|
||||
}
|
||||
else
|
||||
{
|
||||
return { false, refreshError };
|
||||
}
|
||||
}
|
||||
|
||||
if (status < 200 || status >= 300)
|
||||
return { false, ParseErrorMessage(responseBody, "Failed to send game invite.") };
|
||||
|
||||
return { true, std::string() };
|
||||
}
|
||||
|
||||
GameInviteActionResult AcceptGameInviteSync(const std::string &inviteId)
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, std::string(), std::string(), 0, std::string(), "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/sessions/invites/" + inviteId + "/accept";
|
||||
req.body = "{}";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
return { false, std::string(), std::string(), 0, std::string(), ParseErrorMessage(responseBody, "Failed to accept game invite.") };
|
||||
|
||||
const Json responseJson = Json::parse(responseBody, nullptr, false);
|
||||
if (!responseJson.is_object())
|
||||
return { false, std::string(), std::string(), 0, std::string(), "Invalid game invite response." };
|
||||
|
||||
GameInviteActionResult result = {};
|
||||
result.success = true;
|
||||
result.inviteId = JsonStringOrEmpty(responseJson, "inviteId");
|
||||
result.hostIp = JsonStringOrEmpty(responseJson, "hostIp");
|
||||
result.hostPort = JsonIntOrDefault(responseJson, "hostPort", 0);
|
||||
result.hostName = JsonStringOrEmpty(responseJson, "hostName");
|
||||
return result;
|
||||
}
|
||||
|
||||
SocialActionResult DeclineGameInviteSync(const std::string &inviteId)
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/sessions/invites/" + inviteId + "/decline";
|
||||
req.body = "{}";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
return { false, ParseErrorMessage(responseBody, "Failed to decline game invite.") };
|
||||
|
||||
return { true, std::string() };
|
||||
}
|
||||
|
||||
SocialActionResult DeactivateGameInvitesSync()
|
||||
{
|
||||
EnsureInitialized();
|
||||
std::string accessToken;
|
||||
EnterCriticalSection(&g_state.lock);
|
||||
if (g_state.session.valid)
|
||||
accessToken = g_state.session.accessToken;
|
||||
LeaveCriticalSection(&g_state.lock);
|
||||
|
||||
if (accessToken.empty())
|
||||
return { false, "Not signed in to LCELive." };
|
||||
|
||||
RequestContext req = {};
|
||||
req.type = ERequestType::None;
|
||||
req.path = "/api/sessions/invites/deactivate";
|
||||
req.body = "{}";
|
||||
req.authorization = "Bearer " + accessToken;
|
||||
|
||||
DWORD status = 0;
|
||||
std::string responseBody;
|
||||
if (!PerformJsonRequest(req, &status, &responseBody) || status < 200 || status >= 300)
|
||||
return { false, ParseErrorMessage(responseBody, "Failed to deactivate game invites.") };
|
||||
|
||||
return { true, std::string() };
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -67,6 +67,42 @@ namespace Win64LceLive
|
||||
std::string error;
|
||||
};
|
||||
|
||||
struct GameInviteEntry
|
||||
{
|
||||
std::string inviteId;
|
||||
std::string senderAccountId;
|
||||
std::string senderUsername;
|
||||
std::string senderDisplayName;
|
||||
std::string recipientAccountId;
|
||||
std::string recipientUsername;
|
||||
std::string recipientDisplayName;
|
||||
std::string hostIp;
|
||||
int hostPort;
|
||||
std::string hostName;
|
||||
std::string status;
|
||||
bool sessionActive;
|
||||
std::string createdUtc;
|
||||
std::string expiresUtc;
|
||||
};
|
||||
|
||||
struct GameInvitesResult
|
||||
{
|
||||
bool success;
|
||||
std::vector<GameInviteEntry> incoming;
|
||||
std::vector<GameInviteEntry> outgoing;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
struct GameInviteActionResult
|
||||
{
|
||||
bool success;
|
||||
std::string inviteId;
|
||||
std::string hostIp;
|
||||
int hostPort;
|
||||
std::string hostName;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
void Tick();
|
||||
Snapshot GetSnapshot();
|
||||
bool StartDeviceLink();
|
||||
@@ -95,6 +131,12 @@ namespace Win64LceLive
|
||||
SocialActionResult AcceptFriendRequestSync(const std::string& fromAccountId);
|
||||
SocialActionResult DeclineFriendRequestSync(const std::string& fromAccountId);
|
||||
SocialActionResult RemoveFriendSync(const std::string& accountId);
|
||||
|
||||
GameInvitesResult GetGameInvitesSync();
|
||||
SocialActionResult SendGameInviteSync(const std::string& recipientAccountId, const std::string& hostIp, int hostPort, const std::string& hostName);
|
||||
GameInviteActionResult AcceptGameInviteSync(const std::string& inviteId);
|
||||
SocialActionResult DeclineGameInviteSync(const std::string& inviteId);
|
||||
SocialActionResult DeactivateGameInvitesSync();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -66,7 +66,25 @@ typedef DQRNetworkManager::SessionInfo INVITE_INFO;
|
||||
typedef ULONGLONG PlayerUID;
|
||||
typedef ULONGLONG SessionID;
|
||||
typedef PlayerUID GameSessionUID;
|
||||
class INVITE_INFO;
|
||||
struct INVITE_INFO
|
||||
{
|
||||
INVITE_INFO()
|
||||
: netVersion(0)
|
||||
, hostPort(0)
|
||||
, sessionActive(false)
|
||||
{
|
||||
hostIP[0] = 0;
|
||||
hostName[0] = 0;
|
||||
inviteId[0] = 0;
|
||||
}
|
||||
|
||||
unsigned short netVersion;
|
||||
char hostIP[64];
|
||||
int hostPort;
|
||||
wchar_t hostName[32];
|
||||
char inviteId[64];
|
||||
bool sessionActive;
|
||||
};
|
||||
|
||||
#endif // __PS3__
|
||||
|
||||
|
||||
Reference in New Issue
Block a user