From 25f64dd1a03393576cc2a94241169a5e0a3df3fb Mon Sep 17 00:00:00 2001 From: tintinhamans <5984296+tintinhamans@users.noreply.github.com> Date: Wed, 26 Aug 2026 05:31:07 +0200 Subject: [PATCH 1/3] feat(lobby): Add buddy game filtering --- .../Include/GameNetwork/GameSpy/LobbyUtils.h | 1 + .../Source/GameNetwork/GameSpy/LobbyUtils.cpp | 86 +++++++++++++------ 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/Core/GameEngine/Include/GameNetwork/GameSpy/LobbyUtils.h b/Core/GameEngine/Include/GameNetwork/GameSpy/LobbyUtils.h index de8b10ddfe0..01dca008afe 100644 --- a/Core/GameEngine/Include/GameNetwork/GameSpy/LobbyUtils.h +++ b/Core/GameEngine/Include/GameNetwork/GameSpy/LobbyUtils.h @@ -61,4 +61,5 @@ enum LobbyGameModeFilter CPP_11(: Int) LOBBY_FILTER_TEAM, LOBBY_FILTER_FFA, LOBBY_FILTER_AOD, + LOBBY_FILTER_BUDDIES, }; diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp index 6bb72d6f39a..78857343241 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp @@ -110,6 +110,17 @@ static GameWindow *windowSortBuddies = nullptr; static GameSortType theGameSortType = GAMESORT_MAP_ASCENDING; // was ping static Bool sortBuddies = TRUE; + +// The .wnd supplies the ascending arrow as the enabled image and the descending arrow as the disabled image. +static void showSortArrow(GameWindow *window, Bool ascending) +{ + if (window == nullptr) + return; + + window->winHide(FALSE); + window->winEnable(ascending); +} + static void showSortIcons() { if (windowSortAlpha && windowSortPing) @@ -117,38 +128,32 @@ static void showSortIcons() switch (theGameSortType) { case GAMESORT_AGE_ASCENDING: // was alpha - windowSortAlpha->winHide(FALSE); - windowSortAlpha->winEnable(TRUE); + showSortArrow(windowSortAlpha, TRUE); windowSortPing->winHide(TRUE); break; case GAMESORT_AGE_DESCENDING: // was alpha - windowSortAlpha->winHide(FALSE); - windowSortAlpha->winEnable(FALSE); + showSortArrow(windowSortAlpha, FALSE); windowSortPing->winHide(TRUE); break; case GAMESORT_MAP_ASCENDING: // was ping - windowSortPing->winHide(FALSE); - windowSortPing->winEnable(TRUE); + showSortArrow(windowSortPing, TRUE); windowSortAlpha->winHide(TRUE); break; case GAMESORT_MAP_DESCENDING: // was ping - windowSortPing->winHide(FALSE); - windowSortPing->winEnable(FALSE); + showSortArrow(windowSortPing, FALSE); windowSortAlpha->winHide(TRUE); break; } } - if (sortBuddies) + if (windowSortBuddies) { - if (windowSortBuddies) + if (sortBuddies) { - windowSortBuddies->winHide(FALSE); + // true sorts before false. + showSortArrow(windowSortBuddies, FALSE); } - } - else - { - if (windowSortBuddies) + else { windowSortBuddies->winHide(TRUE); } @@ -596,6 +601,19 @@ typedef std::set BuddyGameSet; #endif static BuddyGameSet *theBuddyGames = nullptr; + +#if defined(GENERALS_ONLINE) +static Bool lobbyHasBuddy(int64_t lobbyID) +{ + return theBuddyGames != nullptr && theBuddyGames->count(lobbyID) != 0; +} +#else +static Bool lobbyHasBuddy(GameSpyStagingRoom *room) +{ + return theBuddyGames->find(room) != theBuddyGames->end(); +} +#endif + #if defined(GENERALS_ONLINE) static void populateBuddyGames(std::vector& vecLobbies) #else @@ -611,18 +629,25 @@ static void populateBuddyGames(void) return; } - for (LobbyEntry& lobby : vecLobbies) + // Snapshot once, the map is rebuilt on the HTTP thread and every lobby must be tested against the same list. + const auto mapFriends = pSocialInterface->GetCachedFriendsList(); + if (mapFriends.empty()) + { + return; + } + + for (const LobbyEntry& lobby : vecLobbies) { // is host our friend? - if (pSocialInterface->IsUserFriend(lobby.owner)) + if (mapFriends.contains(lobby.owner)) { theBuddyGames->insert(lobby.lobbyID); } else // does the lobby contain any of our friends { - for (auto member : lobby.members) + for (const LobbyMemberEntry& member : lobby.members) { - if (pSocialInterface->IsUserFriend(member.user_id)) + if (mapFriends.contains(member.user_id)) { theBuddyGames->insert(lobby.lobbyID); break; // its binary, we don't care how many friends @@ -678,8 +703,8 @@ struct GameSortStruct if (sortBuddies) { - const bool g1Buddy = (theBuddyGames && theBuddyGames->count(g1.lobbyID)); - const bool g2Buddy = (theBuddyGames && theBuddyGames->count(g2.lobbyID)); + const Bool g1Buddy = lobbyHasBuddy(g1.lobbyID); + const Bool g2Buddy = lobbyHasBuddy(g2.lobbyID); if (g1Buddy != g2Buddy) return g1Buddy && !g2Buddy; @@ -751,8 +776,8 @@ struct GameSortStruct if (sortBuddies) { - Bool g1HasBuddies = (theBuddyGames->find(g1) != theBuddyGames->end()); - Bool g2HasBuddies = (theBuddyGames->find(g2) != theBuddyGames->end()); + Bool g1HasBuddies = lobbyHasBuddy(g1); + Bool g2HasBuddies = lobbyHasBuddy(g2); if ( g1HasBuddies ^ g2HasBuddies ) { return g1HasBuddies; @@ -788,7 +813,7 @@ static Int insertGame(GameWindow* win, LobbyEntry& lobbyInfo, Bool showMap) } #if defined(GENERALS_ONLINE) // Buddy lobby highlight: - if (theBuddyGames && theBuddyGames->count(lobbyInfo.lobbyID)) + if (lobbyHasBuddy(lobbyInfo.lobbyID)) { const bool nonJoinable = (gameColor == GameSpyColor[GSCOLOR_GAME_CRCMISMATCH]); @@ -1187,13 +1212,20 @@ void RefreshGameListBox(GameWindow* win, Bool showMap) { win->winEnable(true); + populateBuddyGames(vecLobbies); + // filter lobbies by game mode if (theLobbyFilter != LOBBY_FILTER_ALL) { std::vector filtered; + filtered.reserve(vecLobbies.size()); for (Int i = 0; i < (Int)vecLobbies.size(); ++i) { - if (detectGameMode(vecLobbies[i].name) == theLobbyFilter) + const Bool matchesFilter = (theLobbyFilter == LOBBY_FILTER_BUDDIES) + ? lobbyHasBuddy(vecLobbies[i].lobbyID) + : (detectGameMode(vecLobbies[i].name) == theLobbyFilter); + + if (matchesFilter) filtered.push_back(vecLobbies[i]); } vecLobbies = filtered; @@ -1201,6 +1233,7 @@ void RefreshGameListBox(GameWindow* win, Bool showMap) { GadgetListBoxAddEntryText(win, UnicodeString(L"No lobbies currently match this filter"), GameMakeColor(255, 194, 15, 255), -1, -1); GadgetListBoxSetSelected(win, -1); + clearBuddyGames(); return; } } @@ -1208,7 +1241,6 @@ void RefreshGameListBox(GameWindow* win, Bool showMap) // sort our games typedef std::multiset SortedGameList; SortedGameList sgl; - populateBuddyGames(vecLobbies); for (LobbyEntry& lobby : vecLobbies) { sgl.insert(lobby); @@ -1231,6 +1263,8 @@ void RefreshGameListBox(GameWindow* win, Bool showMap) ++i; } + clearBuddyGames(); + // restore selection GadgetListBoxSetSelected(win, indexToSelect); // even for -1, so we can disable the 'Join Game' button // if(prevPos > 10) From a898905e5d2b31e3b2bccc2a04594b761bd9b61e Mon Sep 17 00:00:00 2001 From: tintinhamans <5984296+tintinhamans@users.noreply.github.com> Date: Wed, 26 Aug 2026 20:07:00 +0200 Subject: [PATCH 2/3] feat(lobby): Add hierarchical room navigation --- .../GeneralsOnline/OnlineServices_Init.h | 9 +- .../OnlineServices_RoomsInterface.h | 64 +++- .../GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp | 346 +++++++++++------- .../OnlineServices_RoomsInterface.cpp | 199 +++++++--- 4 files changed, 417 insertions(+), 201 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h index 7768da00e68..385f7d63cbd 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h @@ -152,7 +152,7 @@ class WebSocket void SendData_RoomChatMessage(UnicodeString& msg, bool bIsAction); void SendData_FriendMessage(UnicodeString& msg, int64_t target_user_id); void SendData_LobbyChatMessage(UnicodeString& msg, bool bIsAction, bool bIsAnnouncement, bool bShowAnnouncementToHost); - void SendData_JoinNetworkRoom(int roomID); + void SendData_JoinNetworkRoom(int roomID, uint64_t requestID = 0); void SendData_LeaveNetworkRoom(); void SendData_MarkReady(bool bReady); @@ -230,11 +230,12 @@ enum class ERoomFlags : int class NetworkRoom { public: - NetworkRoom(int roomID, std::string strRoomName, ERoomFlags roomFlags) + NetworkRoom(int roomID, std::string strRoomName, ERoomFlags roomFlags, int parentRoomID = -1) { m_RoomID = roomID; m_strRoomDisplayName.translate(AsciiString(strRoomName.c_str())); m_RoomFlags = roomFlags; + m_ParentRoomID = parentRoomID; } ~NetworkRoom() @@ -243,13 +244,15 @@ class NetworkRoom } int GetRoomID() const { return m_RoomID; } - UnicodeString GetRoomDisplayName() const { return m_strRoomDisplayName; } + const UnicodeString& GetRoomDisplayName() const { return m_strRoomDisplayName; } ERoomFlags GetRoomFlags() const { return m_RoomFlags; } + int GetParentRoomID() const { return m_ParentRoomID; } private: int m_RoomID; UnicodeString m_strRoomDisplayName; ERoomFlags m_RoomFlags = ERoomFlags::ROOM_FLAGS_DEFAULT; + int m_ParentRoomID = -1; }; struct RegionResponse diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h index 42d2e1c2400..c4b5dc80866 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h @@ -6,6 +6,9 @@ #include "OnlineServices_Init.h" #include "Common/MultiplayerSettings.h" +#include +#include + extern NGMPGame* TheNGMPGame; enum class EChatMessageType @@ -98,18 +101,32 @@ class NetworkRoomMember : public NetworkMemberBase bool IsValid() const { return user_id != -1; } }; +struct RoomSelectionResult +{ + std::optional requestID; + std::optional selectedRoomID; + std::optional effectiveRoomID; + std::optional rejectedRoomID; + std::string error; +}; + class NGMP_OnlineServices_RoomsInterface { public: NGMP_OnlineServices_RoomsInterface(); - void GetRoomList(std::function cb); + void GetRoomList(std::function cb); - std::function m_PendingRoomJoinCompleteCallback = nullptr; - void JoinRoom(int roomIndex, std::function onStartCallback, std::function onCompleteCallback); + void JoinRoom(int roomIndex); void LeaveRoom() { + m_CurrentRoomIndex = -1; + m_EffectiveRoomID.reset(); + m_PendingRoomChange.reset(); + m_bRoomSelectionResultsSupported = false; + m_vecRooms.clear(); + std::shared_ptr pWS = NGMP_OnlineServicesManager::GetWebSocket(); if (pWS != nullptr) { @@ -142,6 +159,16 @@ class NGMP_OnlineServices_RoomsInterface m_RosterNeedsRefreshCallback = nullptr; } + void RegisterForRoomChangedCallback(std::function cb) + { + m_RoomChangedCallback = std::move(cb); + } + + void DeregisterForRoomChangedCallback() + { + m_RoomChangedCallback = nullptr; + } + NetworkRoomMember* GetRoomMemberFromIndex(int index) { if (m_mapMembers.size() > index) @@ -180,23 +207,30 @@ class NGMP_OnlineServices_RoomsInterface } } - void Tick() - { + void Tick(); - } + const std::vector& GetGroupRooms() const { return m_vecRooms; } - std::vector GetGroupRooms() - { - return m_vecRooms; - } - - void OnRosterUpdated(std::unordered_map mapMembers); - - int GetCurrentRoomID() const { return m_CurrentRoomID; } + void OnRosterUpdated(std::unordered_map mapMembers, const RoomSelectionResult& selectionResult); + int GetCurrentRoomIndex() const { return m_CurrentRoomIndex; } private: - int m_CurrentRoomID = -1; + struct PendingRoomChange + { + int roomIndex; + int roomID; + uint64_t requestID; + std::chrono::steady_clock::time_point deadline; + }; + + int m_CurrentRoomIndex = -1; + std::optional m_EffectiveRoomID; + std::optional m_PendingRoomChange; + uint64_t m_NextRoomChangeRequestID = 1; + bool m_bRoomSelectionResultsSupported = false; + std::function m_RoomChangedCallback = nullptr; + void ReportRoomJoinFailure(const std::string& error); std::vector m_vecRooms; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp index c270950ce27..25c22f93c60 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp @@ -87,11 +87,11 @@ static Bool isShuttingDown = false; static Bool buttonPushed = false; static const char *nextScreen = nullptr; static Bool raiseMessageBoxes = false; +static UnsignedInt s_lobbyMenuGeneration = 0; static time_t gameListRefreshTime = 0; static const time_t gameListRefreshInterval = 4000; static time_t playerListRefreshTime = 0; -static const time_t playerListRefreshInterval = 6000; -static bool isFirstRosterUpdate = false; +static const time_t playerListRefreshInterval = 4000; void setUnignoreText( WindowLayout *layout, AsciiString nick, GPProfile id); static void doSliderTrack(GameWindow *control, Int val); @@ -587,27 +587,164 @@ static void playerTooltip(GameWindow *window, */ } +// Set while repopulating the combo box, because setting the selection re-sends GCM_SELECTED as if the user had picked it. +static Bool s_populatingLobbyCombo = FALSE; +static const Int LOBBY_COMBO_SEPARATOR_ITEM_DATA = -1; + +static Int FindRoomIndexByID(const std::vector& rooms, Int roomID) +{ + for (Int roomIndex = 0; roomIndex < (Int)rooms.size(); ++roomIndex) + { + if (rooms[roomIndex].GetRoomID() == roomID) + { + return roomIndex; + } + } + + return -1; +} + +static Bool RoomHasLaterSibling(const std::vector& rooms, Int roomIndex) +{ + const Int parentRoomID = rooms[roomIndex].GetParentRoomID(); + for (Int siblingIndex = roomIndex + 1; siblingIndex < (Int)rooms.size(); ++siblingIndex) + { + if (rooms[siblingIndex].GetParentRoomID() == parentRoomID) + { + return TRUE; + } + } + + return FALSE; +} + +static UnicodeString FormatRoomLabel(const std::vector& rooms, Int roomIndex) +{ + UnicodeString label; + const NetworkRoom& room = rooms[roomIndex]; + std::vector ancestors; + Int parentRoomID = room.GetParentRoomID(); + + while (parentRoomID >= 0 && ancestors.size() < rooms.size()) + { + const Int parentIndex = FindRoomIndexByID(rooms, parentRoomID); + if (parentIndex < 0) + { + break; + } + + ancestors.push_back(parentIndex); + parentRoomID = rooms[parentIndex].GetParentRoomID(); + } + + std::reverse(ancestors.begin(), ancestors.end()); + for (size_t ancestorIndex = 1; ancestorIndex < ancestors.size(); ++ancestorIndex) + { + label.concat(RoomHasLaterSibling(rooms, ancestors[ancestorIndex]) ? L"\u2502 " : L" "); + } + + if (!ancestors.empty()) + { + label.concat(RoomHasLaterSibling(rooms, roomIndex) ? L"\u251C\u2500 " : L"\u2514\u2500 "); + } + label.concat(room.GetRoomDisplayName()); + return label; +} + static void PopulateLobbyFilterComboBox(GameWindow* comboBox) { if (comboBox == nullptr) return; extern LobbyGameModeFilter theLobbyFilter; + s_populatingLobbyCombo = TRUE; GadgetComboBoxReset(comboBox); + static const struct + { + const wchar_t* label; + LobbyGameModeFilter filter; + } filterEntries[] = + { + { L"Filter: All", LOBBY_FILTER_ALL }, + { L"Filter: 1v1", LOBBY_FILTER_1V1 }, + { L"Filter: Team Games", LOBBY_FILTER_TEAM }, + { L"Filter: FFA", LOBBY_FILTER_FFA }, + { L"Filter: AOD", LOBBY_FILTER_AOD }, + { L"Filter: Buddies", LOBBY_FILTER_BUDDIES }, + }; + Int idx; - idx = GadgetComboBoxAddEntry(comboBox, UnicodeString(L"Filter: All"), - GameSpyColor[GSCOLOR_DEFAULT]); GadgetComboBoxSetItemData(comboBox, idx, (void*)LOBBY_FILTER_ALL); - idx = GadgetComboBoxAddEntry(comboBox, UnicodeString(L"Filter: 1v1"), - GameSpyColor[GSCOLOR_DEFAULT]); GadgetComboBoxSetItemData(comboBox, idx, (void*)LOBBY_FILTER_1V1); - idx = GadgetComboBoxAddEntry(comboBox, UnicodeString(L"Filter: Team Games"), - GameSpyColor[GSCOLOR_DEFAULT]); GadgetComboBoxSetItemData(comboBox, idx, (void*)LOBBY_FILTER_TEAM); - idx = GadgetComboBoxAddEntry(comboBox, UnicodeString(L"Filter: FFA"), - GameSpyColor[GSCOLOR_DEFAULT]); GadgetComboBoxSetItemData(comboBox, idx, (void*)LOBBY_FILTER_FFA); - idx = GadgetComboBoxAddEntry(comboBox, UnicodeString(L"Filter: AOD"), - GameSpyColor[GSCOLOR_DEFAULT]); GadgetComboBoxSetItemData(comboBox, idx, (void*)LOBBY_FILTER_AOD); - - GadgetComboBoxSetSelectedPos(comboBox, (Int)theLobbyFilter); + Int selectedRoomIdx = -1; + UnicodeString selectedRoomName; + + NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pRoomsInterface != nullptr) + { + const std::vector rooms = pRoomsInterface->GetGroupRooms(); + const Int currentRoomIndex = pRoomsInterface->GetCurrentRoomIndex(); + const Int numRooms = (Int)rooms.size(); + for (Int i = 0; i < numRooms; ++i) + { + const UnicodeString roomLabel = FormatRoomLabel(rooms, i); + idx = GadgetComboBoxAddEntry(comboBox, roomLabel, + GameSpyColor[i == currentRoomIndex ? GSCOLOR_CURRENTROOM : GSCOLOR_ROOM]); + // Room entries use values below the negative separator value. + GadgetComboBoxSetItemData(comboBox, idx, (void*)(intptr_t)(-(i + 2))); + + if (i == currentRoomIndex) + { + selectedRoomIdx = idx; + selectedRoomName = rooms[i].GetRoomDisplayName(); + } + } + + if (numRooms > 0) + { + idx = GadgetComboBoxAddEntry(comboBox, UnicodeString(L" "), GameSpyColor[GSCOLOR_ROOM]); + GadgetComboBoxSetItemData(comboBox, idx, (void*)(intptr_t)LOBBY_COMBO_SEPARATOR_ITEM_DATA); + } + } + + for (const auto& filterEntry : filterEntries) + { + const Bool isActiveFilter = (filterEntry.filter == theLobbyFilter); + idx = GadgetComboBoxAddEntry(comboBox, UnicodeString(filterEntry.label), + GameSpyColor[isActiveFilter ? GSCOLOR_CURRENTROOM : GSCOLOR_DEFAULT]); + GadgetComboBoxSetItemData(comboBox, idx, (void*)filterEntry.filter); + } + + // The collapsed combo always identifies the room. The active filter is indicated by its color only when expanded. + GadgetComboBoxSetSelectedPos(comboBox, selectedRoomIdx); + if (selectedRoomIdx >= 0) + { + GadgetComboBoxSetText(comboBox, selectedRoomName); + } + s_populatingLobbyCombo = FALSE; +} + +static void HandleNetworkRoomChanged(int roomIndex, bool effectiveRoomChanged) +{ + NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pRoomsInterface == nullptr) + return; + + const std::vector& rooms = pRoomsInterface->GetGroupRooms(); + if (roomIndex < 0 || roomIndex >= (int)rooms.size()) + return; + + if (effectiveRoomChanged) + { + GadgetListBoxReset(listboxLobbyChat); + refreshPlayerList(TRUE); + } + + UnicodeString msg; + msg.format(TheGameText->fetch("GUI:LobbyJoined"), rooms[roomIndex].GetRoomDisplayName().str()); + GadgetListBoxAddEntryText(listboxLobbyChat, msg, GameSpyColor[GSCOLOR_DEFAULT], -1, -1); + + refreshGameList(TRUE); + PopulateLobbyFilterComboBox(comboLobbyGroupRooms); } static const char *const rankNames[] = { @@ -1225,6 +1362,8 @@ void NGMP_WOLLobbyMenu_JoinLobbyCallback(EJoinLobbyResult result) //------------------------------------------------------------------------------------------------- void WOLLobbyMenuInit( WindowLayout *layout, void *userData ) { + const UnsignedInt lobbyMenuGeneration = ++s_lobbyMenuGeneration; + // for safety (and sanity) NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); if (pLobbyInterface != nullptr) @@ -1235,7 +1374,6 @@ void WOLLobbyMenuInit( WindowLayout *layout, void *userData ) nextScreen = nullptr; buttonPushed = false; isShuttingDown = false; - isFirstRosterUpdate = true; SetLobbyAttemptHostJoin(FALSE); // not trying to host or join @@ -1269,6 +1407,7 @@ void WOLLobbyMenuInit( WindowLayout *layout, void *userData ) listboxLobbyPlayersID = TheNameKeyGenerator->nameToKey("WOLCustomLobby.wnd:ListboxPlayers"); listboxLobbyPlayers = TheWindowManager->winGetWindowFromId(parent, listboxLobbyPlayersID); + GadgetListBoxRemoveMultiSelect(listboxLobbyPlayers); listboxLobbyPlayers->winSetTooltipFunc(playerTooltip); SetListBoxRowAnimMode(listboxLobbyPlayers, LIST_ROW_ANIM_ID); @@ -1334,17 +1473,11 @@ void WOLLobbyMenuInit( WindowLayout *layout, void *userData ) // register for roster events pRoomsInterface->RegisterForRosterNeedsRefreshCallback([]() - { - if (isFirstRosterUpdate) - { - refreshPlayerList(true); - isFirstRosterUpdate = false; - } - else { refreshPlayerList(false); - } }); + + pRoomsInterface->RegisterForRoomChangedCallback(HandleNetworkRoomChanged); } GrabWindowInfo(); @@ -1408,50 +1541,21 @@ void WOLLobbyMenuInit( WindowLayout *layout, void *userData ) NGMP_OnlineServices_RoomsInterface* pRoomsInterfaceOuter = NGMP_OnlineServicesManager::GetInterface(); if (pRoomsInterfaceOuter != nullptr) { - pRoomsInterfaceOuter->GetRoomList([=]() + pRoomsInterfaceOuter->GetRoomList([=](bool success) { - // attempt to join the first room - pRoomsInterfaceOuter->JoinRoom(0, []() - { - //GadgetListBoxAddEntryText(listboxLobbyChat, UnicodeString(L"Attempting to join room"), GameMakeColor(255, 194, 15, 255), -1, -1); - }, - []() - { - GadgetListBoxReset(listboxLobbyChat); - - NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pRoomsInterface != nullptr) - { - // TODO_NGMP: What can we do if empty? kick them out back to the front end? - if (!pRoomsInterface->GetGroupRooms().empty()) - { - UnicodeString msg; - msg.format(TheGameText->fetch("GUI:LobbyJoined"), pRoomsInterface->GetGroupRooms().at(0).GetRoomDisplayName().str()); - GadgetListBoxAddEntryText(listboxLobbyChat, msg, GameSpyColor[GSCOLOR_DEFAULT], -1, -1); - - // process flag related info messages - ERoomFlags flags = pRoomsInterface->GetGroupRooms().at(0).GetRoomFlags(); - if (flags == ERoomFlags::ROOM_FLAGS_SHOW_ALL_MATCHES) - { - //GadgetListBoxAddEntryText(listboxLobbyChat, UnicodeString(L"\t INFO: This is a special room where the lobby list shows lobbies from ALL rooms, not just the current room, to make it easier to find a match without room hopping."), GameMakeColor(255, 194, 15, 255), -1, -1); - //GadgetListBoxAddEntryText(listboxLobbyChat, UnicodeString(L"\t INFO: Lobbies created here will only show in here. The members list & chat will also only show players in this room."), GameMakeColor(255, 194, 15, 255), -1, -1); - } - } - else - { - GadgetListBoxAddEntryText(listboxLobbyChat, UnicodeString(L"\t ERROR: No rooms are available. Try logging in again."), GameMakeColor(255, 0, 0, 255), -1, -1); - } - } - - - // refresh on join - refreshPlayerList(TRUE); + if (lobbyMenuGeneration != s_lobbyMenuGeneration || buttonPushed || isShuttingDown || listboxLobbyChat == nullptr) + { + return; + } - refreshGameList(TRUE); - RefreshGameListBoxes(); + const std::vector& rooms = pRoomsInterfaceOuter->GetGroupRooms(); + if (!success || rooms.empty()) + { + GadgetListBoxAddEntryText(listboxLobbyChat, UnicodeString(L"\t ERROR: No rooms are available. Try logging in again."), GameMakeColor(255, 0, 0, 255), -1, -1); + return; + } - PopulateLobbyFilterComboBox(comboLobbyGroupRooms); - }); + pRoomsInterfaceOuter->JoinRoom(0); }); } @@ -1517,13 +1621,21 @@ static void shutdownComplete( WindowLayout *layout ) //------------------------------------------------------------------------------------------------- void WOLLobbyMenuShutdown( WindowLayout *layout, void *userData ) { + ++s_lobbyMenuGeneration; + + NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pRoomsInterface != nullptr) + { + pRoomsInterface->DeregisterForChatCallback(); + pRoomsInterface->DeregisterForRosterNeedsRefreshCallback(); + pRoomsInterface->DeregisterForRoomChangedCallback(); + } + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); if (pLobbyInterface != nullptr) { pLobbyInterface->DeregisterForCreateLobbyCallback(); pLobbyInterface->DeregisterForJoinLobbyCallback(); - pLobbyInterface->DeregisterForChatCallback(); - pLobbyInterface->DeregisterForRosterNeedsRefreshCallback(); pLobbyInterface->DeregisterForSearchForLobbiesCallback(); } @@ -1653,6 +1765,11 @@ void refreshGameList( Bool forceRefresh ) { #if defined(GENERALS_ONLINE) RefreshGameListBoxes(); + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + pLobbyInterface->ConsumeLobbyListDirtyFlag(); + } gameListRefreshTime = timeGetTime(); #else if (TheGameSpyInfo->hasStagingRoomListChanged()) @@ -1768,8 +1885,6 @@ void WOLLobbyMenuUpdate( WindowLayout * layout, void *userData) { const bool bShouldAutoRefresh = true; - pLobbyInterface->ConsumeLobbyListDirtyFlag(); - if (bShouldAutoRefresh) { refreshGameList(false); @@ -2560,7 +2675,7 @@ WindowMsgHandledType WOLLobbyMenuSystem( GameWindow *window, UnsignedInt msg, txtInput.trim(); if (!txtInput.isEmpty()) { - if (!LobbyChatSlowmodeAllowsSend()) + if (!LobbyChatSlowmodeAllowsSend()) { break; } @@ -2579,88 +2694,47 @@ WindowMsgHandledType WOLLobbyMenuSystem( GameWindow *window, UnsignedInt msg, //--------------------------------------------------------------------------------------------- case GCM_SELECTED: { - if (s_tryingToHostOrJoin) + if (s_tryingToHostOrJoin || s_populatingLobbyCombo) break; - //NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); - //if (pRoomsInterface == nullptr) - //{ - // break; - //} + NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); extern LobbyGameModeFilter theLobbyFilter; GameWindow *control = (GameWindow *)mData1; Int controlID = control->winGetWindowId(); if( controlID == comboLobbyGroupRoomsID ) { - /* - int rowSelected = -1; - GadgetComboBoxGetSelectedPos(control, &rowSelected); - - DEBUG_LOG(("Row selected = %d", rowSelected)); - if (rowSelected >= 0) + Int pos = -1; + GadgetComboBoxGetSelectedPos(comboLobbyGroupRooms, &pos); + if (pos >= 0) { - Int groupID; - groupID = (Int)GadgetComboBoxGetItemData(comboLobbyGroupRooms, rowSelected); - //DEBUG_LOG(("ItemData was %d, current Group Room is %d", groupID, TheGameSpyInfo->getCurrentGroupRoom())); -// did it change? - if (groupID != pRoomsInterface->GetCurrentRoomID()) + Int itemData = (Int)GadgetComboBoxGetItemData(comboLobbyGroupRooms, pos); + if (itemData == LOBBY_COMBO_SEPARATOR_ITEM_DATA) { - // join - pRoomsInterface->JoinRoom(groupID, [=]() - { - //GadgetListBoxAddEntryText(listboxLobbyChat, UnicodeString(L"Attempting to join room"), GameMakeColor(255, 194, 15, 255), -1, -1); - }, - [=]() - { - // TODO_NGMP: There are two cb lamdas for this, flatten them - GadgetListBoxReset(listboxLobbyChat); - - UnicodeString msg; - msg.format(TheGameText->fetch("GUI:LobbyJoined"), pRoomsInterface->GetGroupRooms().at(groupID).GetRoomDisplayName().str()); - GadgetListBoxAddEntryText(listboxLobbyChat, msg, GameSpyColor[GSCOLOR_DEFAULT], -1, -1); - - - // process flag related info messages - ERoomFlags flags = pRoomsInterface->GetGroupRooms().at(groupID).GetRoomFlags(); - if (flags == ERoomFlags::ROOM_FLAGS_SHOW_ALL_MATCHES) - { - //GadgetListBoxAddEntryText(listboxLobbyChat, UnicodeString(L"\t INFO: This is a special room where the lobby list shows lobbies from ALL rooms, not just the current room, to make it easier to find a match without room hopping."), GameMakeColor(255, 194, 15, 255), -1, -1); - //GadgetListBoxAddEntryText(listboxLobbyChat, UnicodeString(L"\t INFO: Lobbies created here will only show in here. The members list & chat will also only show players in this room."), GameMakeColor(255, 194, 15, 255), -1, -1); - } - - // refresh on join - refreshPlayerList(TRUE); - - RefreshGameListBoxes(); - - populateGroupRoomListbox(comboLobbyGroupRooms); - }); + PopulateLobbyFilterComboBox(comboLobbyGroupRooms); } - - // TODO_NGMP: What does TheGameSpyConfig->restrictGamesToLobby() do? - /* if (groupID && groupID != TheGameSpyInfo->getCurrentGroupRoom()) + else if (itemData < 0) { - TheGameSpyInfo->leaveGroupRoom(); - TheGameSpyInfo->joinGroupRoom(groupID); - if (TheGameSpyConfig->restrictGamesToLobby()) + if (pRoomsInterface != nullptr) { - TheGameSpyInfo->clearStagingRoomList(); - RefreshGameListBoxes(); - PeerRequest req; - req.peerRequestType = PeerRequest::PEERREQUEST_STARTGAMELIST; - req.gameList.restrictGameList = TRUE; - TheGameSpyPeerMessageQueue->addRequest(req); + const Int roomIndex = -itemData - 2; + const std::vector& rooms = pRoomsInterface->GetGroupRooms(); + if (roomIndex >= 0 && roomIndex < (Int)rooms.size() + && roomIndex != pRoomsInterface->GetCurrentRoomIndex()) + { + theLobbyFilter = LOBBY_FILTER_ALL; + pRoomsInterface->JoinRoom(roomIndex); + } } + PopulateLobbyFilterComboBox(comboLobbyGroupRooms); + } + else + { + theLobbyFilter = (LobbyGameModeFilter)itemData; + refreshGameList(TRUE); + PopulateLobbyFilterComboBox(comboLobbyGroupRooms); } - } - */ - Int pos = -1; - GadgetComboBoxGetSelectedPos(comboLobbyGroupRooms, &pos); - if (pos >= 0) - theLobbyFilter = (LobbyGameModeFilter)(Int)GadgetComboBoxGetItemData(comboLobbyGroupRooms, pos); - RefreshGameListBoxes(); } } break; @@ -2960,7 +3034,7 @@ WindowMsgHandledType WOLLobbyMenuSystem( GameWindow *window, UnsignedInt msg, // Send the message if (!handleLobbySlashCommands(txtInput)) { - if (!LobbyChatSlowmodeAllowsSend()) + if (!LobbyChatSlowmodeAllowsSend()) { break; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp index 2c2e1d394e5..130b21dbf77 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp @@ -270,11 +270,15 @@ void WebSocket::SendData_MarkReady(bool bReady) } -void WebSocket::SendData_JoinNetworkRoom(int roomID) +void WebSocket::SendData_JoinNetworkRoom(int roomID, uint64_t requestID) { nlohmann::json j; j["msg_id"] = EWebSocketMessageID::NETWORK_ROOM_CHANGE_ROOM; j["room"] = roomID; + if (requestID != 0) + { + j["request_id"] = requestID; + } std::string strBody = j.dump(); Send(strBody.c_str()); @@ -1257,10 +1261,32 @@ void WebSocket::Tick() mapMembers.emplace(newMember.user_id, newMember); } + RoomSelectionResult selectionResult; + if (jsonObject.contains("selected_room_id") && jsonObject["selected_room_id"].is_number_integer()) + { + selectionResult.selectedRoomID = jsonObject["selected_room_id"].get(); + } + if (jsonObject.contains("effective_room_id") && jsonObject["effective_room_id"].is_number_integer()) + { + selectionResult.effectiveRoomID = jsonObject["effective_room_id"].get(); + } + if (jsonObject.contains("rejected_room_id") && jsonObject["rejected_room_id"].is_number_integer()) + { + selectionResult.rejectedRoomID = jsonObject["rejected_room_id"].get(); + } + if (jsonObject.contains("room_selection_error") && jsonObject["room_selection_error"].is_string()) + { + jsonObject["room_selection_error"].get_to(selectionResult.error); + } + if (jsonObject.contains("request_id") && jsonObject["request_id"].is_number_unsigned()) + { + selectionResult.requestID = jsonObject["request_id"].get(); + } + NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); if (pRoomsInterface != nullptr) { - pRoomsInterface->OnRosterUpdated(mapMembers); + pRoomsInterface->OnRosterUpdated(std::move(mapMembers), selectionResult); } } break; @@ -1542,10 +1568,15 @@ NGMP_OnlineServices_RoomsInterface::NGMP_OnlineServices_RoomsInterface() } -void NGMP_OnlineServices_RoomsInterface::GetRoomList(std::function cb) +void NGMP_OnlineServices_RoomsInterface::GetRoomList(std::function cb) { m_vecRooms.clear(); - // Cache our buddies on lobby list + m_CurrentRoomIndex = -1; + m_EffectiveRoomID.reset(); + m_PendingRoomChange.reset(); + m_bRoomSelectionResultsSupported = false; + + // Cache our buddies on lobby list NGMP_OnlineServices_SocialInterface* pSocialInterface = NGMP_OnlineServicesManager::GetInterface(); if (pSocialInterface != nullptr) @@ -1558,77 +1589,84 @@ void NGMP_OnlineServices_RoomsInterface::GetRoomList(std::function c NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendGETRequest(strURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, [=](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq) { + if (!bSuccess || statusCode != 200) + { + cb(false); + return; + } + try { + std::vector rooms; nlohmann::json jsonObject = nlohmann::json::parse(strBody); + const bool roomSelectionResultsSupported = jsonObject.value("supports_room_selection_results", false); for (const auto& roomEntryIter : jsonObject["rooms"]) { int id = 0; std::string strName; - ERoomFlags flags; - + ERoomFlags flags = ERoomFlags::ROOM_FLAGS_DEFAULT; + int parentRoomID = -1; roomEntryIter["id"].get_to(id); roomEntryIter["name"].get_to(strName); - roomEntryIter["flags"].get_to(flags); - NetworkRoom roomEntry(id, strName, flags); - - m_vecRooms.push_back(roomEntry); + if (roomEntryIter.contains("flags") && !roomEntryIter["flags"].is_null()) + { + roomEntryIter["flags"].get_to(flags); + } + if (roomEntryIter.contains("parent_id") && !roomEntryIter["parent_id"].is_null()) + { + roomEntryIter["parent_id"].get_to(parentRoomID); + } + rooms.emplace_back(id, strName, flags, parentRoomID); } - cb(); + m_vecRooms = std::move(rooms); + m_bRoomSelectionResultsSupported = roomSelectionResultsSupported; + + cb(true); return; } - catch (...) + catch (const std::exception& exception) { - + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] Failed to parse room list: %s", exception.what()); } - // TODO_NGMP: Error handling - cb(); + cb(false); return; }); } -void NGMP_OnlineServices_RoomsInterface::JoinRoom(int roomIndex, std::function onStartCallback, std::function onCompleteCallback) +void NGMP_OnlineServices_RoomsInterface::JoinRoom(int roomIndex) { - // TODO_NGMP: Safety - NOW FIXED with null checks - - // TODO_NGMP: Remove this, its no longer a call really, or make a call - if (onStartCallback != nullptr) + const std::vector& rooms = GetGroupRooms(); + if (roomIndex < 0 || roomIndex >= (int)rooms.size()) { - onStartCallback(); + ReportRoomJoinFailure(std::format("Invalid room index {}.", roomIndex)); + return; } - m_CurrentRoomID = roomIndex; - // TODO_NGMP: What if there are zero rooms? e.g. the service request failed - NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pRoomsInterface != nullptr) + std::shared_ptr pWS = NGMP_OnlineServicesManager::GetWebSocket(); + if (pWS == nullptr) { - if (!pRoomsInterface->GetGroupRooms().empty()) - { - // if the room doesnt exist, try the first room - if (roomIndex < 0 || roomIndex >= pRoomsInterface->GetGroupRooms().size()) - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] Invalid room index %d, using first room", roomIndex); - roomIndex = 0; - } - - NetworkRoom targetNetworkRoom = pRoomsInterface->GetGroupRooms().at(roomIndex); - - std::shared_ptr pWS = NGMP_OnlineServicesManager::GetWebSocket();; - if (pWS != nullptr) - { - pWS->SendData_JoinNetworkRoom(targetNetworkRoom.GetRoomID()); - } - } + ReportRoomJoinFailure("The room service is not connected."); + return; } - if (onCompleteCallback != nullptr) + if (m_PendingRoomChange.has_value()) { - onCompleteCallback(); + ReportRoomJoinFailure("Another room change is already in progress."); + return; } + + const uint64_t requestID = m_NextRoomChangeRequestID++; + m_PendingRoomChange = PendingRoomChange{ + roomIndex, + rooms[roomIndex].GetRoomID(), + requestID, + std::chrono::steady_clock::now() + std::chrono::seconds(10) + }; + pWS->SendData_JoinNetworkRoom(rooms[roomIndex].GetRoomID(), requestID); } std::unordered_map& NGMP_OnlineServices_RoomsInterface::GetMembersListForCurrentRoom() @@ -1646,16 +1684,83 @@ void NGMP_OnlineServices_RoomsInterface::SendChatMessageToCurrentRoom(UnicodeStr } } -void NGMP_OnlineServices_RoomsInterface::OnRosterUpdated(std::unordered_map mapMembers) +void NGMP_OnlineServices_RoomsInterface::OnRosterUpdated(std::unordered_map mapMembers, + const RoomSelectionResult& selectionResult) { - m_mapMembers = mapMembers; + m_mapMembers = std::move(mapMembers); + + int changedRoomIndex = -1; + bool effectiveRoomChanged = true; + bool refreshRoster = true; + std::string roomJoinFailure; + if (m_PendingRoomChange.has_value()) + { + const PendingRoomChange& pendingRoomChange = *m_PendingRoomChange; + const bool requestMatches = !selectionResult.requestID.has_value() + || pendingRoomChange.requestID == *selectionResult.requestID; + if (requestMatches && selectionResult.rejectedRoomID == pendingRoomChange.roomID) + { + roomJoinFailure = selectionResult.error.empty() ? "The room selection was rejected." : selectionResult.error; + m_PendingRoomChange.reset(); + } + else + { + const bool selectionMatches = requestMatches + && (selectionResult.selectedRoomID.has_value() + ? pendingRoomChange.roomID == *selectionResult.selectedRoomID + : !m_bRoomSelectionResultsSupported); + if (selectionMatches) + { + if (selectionResult.effectiveRoomID.has_value()) + { + effectiveRoomChanged = !m_EffectiveRoomID.has_value() + || *m_EffectiveRoomID != *selectionResult.effectiveRoomID; + m_EffectiveRoomID = selectionResult.effectiveRoomID; + refreshRoster = effectiveRoomChanged; + } + m_CurrentRoomIndex = pendingRoomChange.roomIndex; + changedRoomIndex = m_CurrentRoomIndex; + m_PendingRoomChange.reset(); + } + } + } + + if (changedRoomIndex >= 0 && m_RoomChangedCallback != nullptr) + { + m_RoomChangedCallback(changedRoomIndex, effectiveRoomChanged); + } + if (!roomJoinFailure.empty()) + { + ReportRoomJoinFailure(roomJoinFailure); + } std::scoped_lock lock(m_rosterCallbackMutex); - if (m_RosterNeedsRefreshCallback != nullptr) + if (refreshRoster && m_RosterNeedsRefreshCallback != nullptr) { m_RosterNeedsRefreshCallback(); } } +void NGMP_OnlineServices_RoomsInterface::Tick() +{ + if (m_PendingRoomChange.has_value() + && std::chrono::steady_clock::now() >= m_PendingRoomChange->deadline) + { + m_PendingRoomChange.reset(); + ReportRoomJoinFailure("The room change timed out. Please try again."); + } +} + +void NGMP_OnlineServices_RoomsInterface::ReportRoomJoinFailure(const std::string& error) +{ + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] Room change failed: %s", error.c_str()); + if (m_OnChatCallback != nullptr) + { + UnicodeString message; + message = L"Couldn't join that room. Please try again."; + m_OnChatCallback(message, GameMakeColor(255, 0, 0, 255)); + } +} + From da8e41a64751afa29ee624631ea49513d4e9aeeb Mon Sep 17 00:00:00 2001 From: tintinhamans <5984296+tintinhamans@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:27:36 +0200 Subject: [PATCH 3/3] fix(lobby): Preserve single presence selection --- .../GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp | 60 ++++++------------- 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp index 25c22f93c60..c0b15f45d61 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp @@ -901,23 +901,12 @@ void PopulateLobbyPlayerListbox() pStatsInterface->findPlayerStatsByBatch(vecUserStatsToRequest, [=](bool bSuccess) { // NOTE: We dont clear until we get a response, so there's no period where the box is empty - // save off old selection - Int maxSelectedItems = GadgetListBoxGetNumEntries(listboxLobbyPlayers); - Int* selectedIndices; - GadgetListBoxGetSelected(listboxLobbyPlayers, (Int*)(&selectedIndices)); - std::set selectedUserIDs; - Int numSelected = 0; - for (Int i = 0; i < maxSelectedItems; ++i) - { - if (selectedIndices[i] < 0) - { - break; - } - ++numSelected; - - int profileID = (int)GadgetListBoxGetItemData(listboxLobbyPlayers, selectedIndices[i], 0); - selectedUserIDs.insert(profileID); - } + Int selectedIndex = -1; + GadgetListBoxGetSelected(listboxLobbyPlayers, &selectedIndex); + const Bool hadSelection = selectedIndex >= 0; + const Int selectedUserID = hadSelection + ? (Int)GadgetListBoxGetItemData(listboxLobbyPlayers, selectedIndex, 0) + : 0; // save off old top entry Int previousTopIndex = GadgetListBoxGetTopVisibleEntry(listboxLobbyPlayers); @@ -926,7 +915,7 @@ void PopulateLobbyPlayerListbox() m_vecUsersProcessed.clear(); GadgetListBoxReset(listboxLobbyPlayers); - std::set indicesToSelect; + Int indexToSelect = -1; // by this point, all stats should be cached - they were either already cached, or we just got them back from the service // sort @@ -1106,35 +1095,20 @@ void PopulateLobbyPlayerListbox() Int index = insertPlayerInListbox(pi, colorToUse); // TODO_NGMP: Use int for user ID like gamespy did, or move everything to uint64 - std::set::const_iterator selIt = selectedUserIDs.find(netRoomMember.user_id); - if (selIt != selectedUserIDs.end()) + if (hadSelection && netRoomMember.user_id == selectedUserID) { - indicesToSelect.insert(index); + indexToSelect = index; } } - // restore selection - if (indicesToSelect.size()) - { - std::set::const_iterator indexIt = indicesToSelect.begin(); - const size_t count = indicesToSelect.size(); - size_t index = 0; - Int* newIndices = NEW Int[count]; - while (index < count) - { - newIndices[index] = *indexIt; - DEBUG_LOG(("Queueing up index %d to re-select", *indexIt)); - ++index; - ++indexIt; - } - GadgetListBoxSetSelected(listboxLobbyPlayers, newIndices, count); - delete[] newIndices; - } - - if (indicesToSelect.size() != numSelected) - { - TheWindowManager->winSetLoneWindow(NULL); - } + if (indexToSelect >= 0) + { + GadgetListBoxSetSelected(listboxLobbyPlayers, indexToSelect); + } + else if (hadSelection) + { + TheWindowManager->winSetLoneWindow(NULL); + } }); /*