diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index f37a4d59..9f4b6ac4 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 338d1905..b8e18444 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); diff --git a/README.md b/README.md index 417acbb6..e5ab35f3 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,11 @@ This project is based on source code of Minecraft Legacy Console Edition v1.6.05 ## Latest: +Dedicated server player list fix: +- The Tab player list now correctly shows all connected players on dedicated servers. Previously only the local player was visible because remote players were never registered in the client's network player tracking when their `AddPlayerPacket` arrived +- The dedicated server's phantom host entry (slot 0, empty name) is now filtered from the list +- Players are properly removed from the list when they disconnect, using gamertag matching since dedicated server XUIDs are not available on the client + SRV record support and async join refactor: - Added DNS SRV record resolution (`_minecraft._tcp.`), matching Java Edition behavior. Players can connect using just a domain name (e.g. `play.example.com`) and the client will automatically look up the correct server address and port from DNS - Refactored the async server joining system: replaced boolean flags with a clean `eJoinState` enum state machine, moved connection progress handling into a dedicated `UIScene_ConnectingProgress` class with attempt counter and cancel support, and added a `FinalizeJoin()` separation so the recv thread only starts after the UI confirms success