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);