From 00d1d23cca2d407a26d7c9fd4b4e257d7b200bda Mon Sep 17 00:00:00 2001 From: itsRevela Date: Thu, 26 Mar 2026 22:25:40 -0500 Subject: [PATCH] Fix player list not showing all players on dedicated servers Register remote players in the client's IQNet array when their AddPlayerPacket arrives, so they appear in the Tab player list. Previously only the host and local player were registered. Also filter the dedicated server's phantom host entry (slot 0, empty gamertag) from the UI, fix tick() to update entries by smallId instead of sequential index, and fix player removal to use gamertag matching since XUIDs are 0 on dedicated servers. --- Minecraft.Client/ClientConnection.cpp | 45 +++++++++++++++---- .../Common/UI/UIScene_InGameInfoMenu.cpp | 39 ++++++++++++---- 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index a80af5d2c..df2ee6273 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -917,6 +917,36 @@ void ClientConnection::handleAddPlayer(shared_ptr packet) } } + // Client-side registration: if we still have no IQNet entry for this remote + // player, create one so they appear in the Tab player list. + // Find the first available IQNet slot (customData == 0, skip slot 0 which + // is the host). We can't use packet->m_playerIndex directly because on + // dedicated servers the game-level player index starts at 0 for real + // players, conflicting with the IQNet host slot. + if (matchedQNetPlayer == nullptr) + { + for (int s = 1; s < MINECRAFT_NET_MAX_PLAYERS; ++s) + { + IQNetPlayer* qp = &IQNet::m_player[s]; + if (qp->GetCustomDataValue() == 0 && qp->m_gamertag[0] == 0) + { + BYTE smallId = static_cast(s); + qp->m_smallId = smallId; + qp->m_isRemote = true; + qp->m_isHostPlayer = false; + qp->m_resolvedXuid = pktXuid; + wcsncpy_s(qp->m_gamertag, 32, packet->name.c_str(), _TRUNCATE); + if (smallId >= IQNet::s_playerCount) + IQNet::s_playerCount = smallId + 1; + + extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager; + g_pPlatformNetworkManager->NotifyPlayerJoined(qp); + matchedQNetPlayer = qp; + break; + } + } + } + if (matchedQNetPlayer != nullptr) { // Store packet-authoritative XUID on this network slot so later lookups by XUID @@ -1088,28 +1118,27 @@ void ClientConnection::handleRemoveEntity(shared_ptr packe for (int i = 0; i < packet->ids.length; i++) { shared_ptr entity = getEntity(packet->ids[i]); - if (entity != nullptr && entity->GetType() == eTYPE_PLAYER) + if (entity != nullptr) { shared_ptr player = dynamic_pointer_cast(entity); if (player != nullptr) { - PlayerUID xuid = player->getXuid(); - INetworkPlayer* np = g_NetworkManager.GetPlayerByXuid(xuid); - if (np != nullptr) + // Match by gamertag in the IQNet array (XUID may be 0 on dedicated servers) + for (int s = 1; s < MINECRAFT_NET_MAX_PLAYERS; ++s) { - NetworkPlayerXbox* npx = (NetworkPlayerXbox*)np; - IQNetPlayer* qp = npx->GetQNetPlayer(); - if (qp != nullptr) + IQNetPlayer* qp = &IQNet::m_player[s]; + if (qp->GetCustomDataValue() != 0 && + _wcsicmp(qp->m_gamertag, player->getName().c_str()) == 0) { extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager; g_pPlatformNetworkManager->NotifyPlayerLeaving(qp); qp->m_smallId = 0; qp->m_isRemote = false; qp->m_isHostPlayer = false; - // Clear resolved id to avoid stale XUID -> player matches after disconnect. qp->m_resolvedXuid = INVALID_XUID; qp->m_gamertag[0] = 0; qp->SetCustomDataValue(0); + break; } } } diff --git a/Minecraft.Client/Common/UI/UIScene_InGameInfoMenu.cpp b/Minecraft.Client/Common/UI/UIScene_InGameInfoMenu.cpp index 338d1905c..b8e184445 100644 --- a/Minecraft.Client/Common/UI/UIScene_InGameInfoMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_InGameInfoMenu.cpp @@ -27,8 +27,15 @@ UIScene_InGameInfoMenu::UIScene_InGameInfoMenu(int iPad, void *initData, UILayer { PlayerInfo *info = BuildPlayerInfo(player); + // Skip the dedicated server's phantom host entry (slot 0, empty name) + if (info->m_smallId == 0 && info->m_name.empty()) + { + delete info; + continue; + } + m_players.push_back(info); - m_playerList.addItem(info->m_name, info->m_colorState, info->m_voiceStatus); + m_playerList.addItem(info->m_name, info->m_colorState, info->m_voiceStatus); } } @@ -174,8 +181,15 @@ void UIScene_InGameInfoMenu::handleReload() { PlayerInfo *info = BuildPlayerInfo(player); + // Skip the dedicated server's phantom host entry (slot 0, empty name) + if (info->m_smallId == 0 && info->m_name.empty()) + { + delete info; + continue; + } + m_players.push_back(info); - m_playerList.addItem(info->m_name, info->m_colorState, info->m_voiceStatus); + m_playerList.addItem(info->m_name, info->m_colorState, info->m_voiceStatus); } } @@ -202,23 +216,22 @@ void UIScene_InGameInfoMenu::tick() { UIScene::tick(); - // Update players by index + // Update players by their stored smallId (not sequential index, which can mismatch + // when entries like the dedicated server host are filtered from the UI list) for(DWORD i = 0; i < m_players.size(); ++i) { - INetworkPlayer *player = g_NetworkManager.GetPlayerByIndex( i ); + INetworkPlayer *player = g_NetworkManager.GetPlayerBySmallId( m_players[i]->m_smallId ); if(player != nullptr) { PlayerInfo *info = BuildPlayerInfo(player); - m_players[i]->m_smallId = info->m_smallId; - if(info->m_voiceStatus != m_players[i]->m_voiceStatus) { m_players[i]->m_voiceStatus = info->m_voiceStatus; m_playerList.setVOIPIcon(i, info->m_voiceStatus); } - + if(info->m_colorState != m_players[i]->m_colorState) { m_players[i]->m_colorState = info->m_colorState; @@ -424,11 +437,19 @@ void UIScene_InGameInfoMenu::OnPlayerChanged(void *callbackParam, INetworkPlayer // If the player is joining if(!leaving) { + PlayerInfo *info = scene->BuildPlayerInfo(pPlayer); + + // Skip the dedicated server's phantom host entry (slot 0, empty name) + if (pPlayer->GetSmallId() == 0 && info->m_name.empty()) + { + delete info; + return; + } + app.DebugPrintf(" Player \"%ls\" not found, adding\n", pPlayer->GetOnlineName()); - PlayerInfo *info = scene->BuildPlayerInfo(pPlayer); scene->m_players.push_back(info); - + // Note that the tick updates buttons every tick so it's only really important that we // add the button (not the order or content) scene->m_playerList.addItem(info->m_name, info->m_colorState, info->m_voiceStatus);