diff --git a/.gitea/workflows/nightly.yml.rej b/.gitea/workflows/nightly.yml.rej deleted file mode 100644 index 2bbce43e..00000000 --- a/.gitea/workflows/nightly.yml.rej +++ /dev/null @@ -1,20 +0,0 @@ -diff a/.gitea/workflows/nightly.yml b/.gitea/workflows/nightly.yml (rejected hunks) -@@ -4,15 +4,12 @@ - workflow_dispatch: - push: - branches: -- - 'main' -+ - main - paths-ignore: - - '.gitignore' - - '*.md' -- - '.gitea/**' -- - '!.gitea/workflows/nightly.yml' -- --permissions: -- contents: write -+ - '.forgejo/**' -+ - '!.forgejo/workflows/nightly.yml' - - concurrency: - group: nightly diff --git a/1429.patch b/1429.patch new file mode 100644 index 00000000..be3fd62f --- /dev/null +++ b/1429.patch @@ -0,0 +1,454 @@ +From 00d1d23cca2d407a26d7c9fd4b4e257d7b200bda Mon Sep 17 00:00:00 2001 +From: itsRevela +Date: Thu, 26 Mar 2026 22:25:40 -0500 +Subject: [PATCH 1/3] 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 a80af5d2c9..df2ee62736 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 338d1905cd..b8e1844456 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); + +From 75eb646becfbae8090f76b44dd690b88194a4ce6 Mon Sep 17 00:00:00 2001 +From: itsRevela +Date: Thu, 26 Mar 2026 22:25:58 -0500 +Subject: [PATCH 2/3] Fix player list map icon colors to match map markers + +The tab player list and teleport menu now show the correct map marker +color for each player. The icon is computed using the same hash as the +map renderer (getRandomPlayerMapIcon) and stored by player name, +bypassing the unreliable small-ID lookup that produced wrong colors +on dedicated servers. +--- + Minecraft.Client/ClientConnection.cpp | 27 +++++++++++++++ + Minecraft.Client/Common/Consoles_App.cpp | 34 +++++++++++++++++++ + Minecraft.Client/Common/Consoles_App.h | 4 +++ + .../Common/UI/UIScene_InGameInfoMenu.cpp | 2 +- + .../Common/UI/UIScene_TeleportMenu.cpp | 4 +-- + 5 files changed, 68 insertions(+), 3 deletions(-) + +diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp +index df2ee62736..7116f311cf 100644 +--- a/Minecraft.Client/ClientConnection.cpp ++++ b/Minecraft.Client/ClientConnection.cpp +@@ -66,6 +66,30 @@ + #include "..\Minecraft.World\GenericStats.h" + #endif + ++namespace ++{ ++ char mapIconToFrame(char iconSlot) ++ { ++ if (iconSlot >= 8) return iconSlot - 4; ++ return iconSlot; ++ } ++ ++ // Same hash as getRandomPlayerMapIcon in MapItemSavedData.cpp, returning ++ // the Iggy/SWF frame index (0-7) instead of the raw icon slot. ++ char computePlayerMapFrame(int entityId, int playerIndex) ++ { ++ static const char PLAYER_MAP_ICON_SLOTS[] = { 0, 1, 2, 3, 8, 9, 10, 11 }; ++ unsigned int seed = static_cast(entityId); ++ seed ^= static_cast(playerIndex * 0x9E3779B9u); ++ seed ^= (seed >> 16); ++ seed *= 0x7FEB352Du; ++ seed ^= (seed >> 15); ++ seed *= 0x846CA68Bu; ++ seed ^= (seed >> 16); ++ return mapIconToFrame(PLAYER_MAP_ICON_SLOTS[seed % 8]); ++ } ++} ++ + ClientConnection::ClientConnection(Minecraft *minecraft, const wstring& ip, int port) + { + // 4J Stu - No longer used as we use the socket version below. +@@ -377,6 +401,7 @@ void ClientConnection::handleLogin(shared_ptr packet) + + BYTE networkSmallId = getSocket()->getSmallId(); + app.UpdatePlayerInfo(networkSmallId, packet->m_playerIndex, packet->m_uiGamePrivileges); ++ app.SetPlayerMapIcon(minecraft->player->getName().c_str(), computePlayerMapFrame(packet->clientVersion, packet->m_playerIndex)); + minecraft->player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_All, packet->m_uiGamePrivileges); + + // Assume all privileges are on, so that the first message we see only indicates things that have been turned off +@@ -447,6 +472,7 @@ void ClientConnection::handleLogin(shared_ptr packet) + + BYTE networkSmallId = getSocket()->getSmallId(); + app.UpdatePlayerInfo(networkSmallId, packet->m_playerIndex, packet->m_uiGamePrivileges); ++ app.SetPlayerMapIcon(player->getName().c_str(), computePlayerMapFrame(packet->clientVersion, packet->m_playerIndex)); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_All, packet->m_uiGamePrivileges); + + // Assume all privileges are on, so that the first message we see only indicates things that have been turned off +@@ -976,6 +1002,7 @@ void ClientConnection::handleAddPlayer(shared_ptr packet) + player->setPlayerIndex( packet->m_playerIndex ); + player->setCustomSkin( packet->m_skinId ); + player->setCustomCape( packet->m_capeId ); ++ app.SetPlayerMapIcon(packet->name.c_str(), computePlayerMapFrame(packet->id, packet->m_playerIndex)); + player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_All, packet->m_uiGamePrivileges); + + if (!player->customTextureUrl.empty() && player->customTextureUrl.substr(0, 3).compare(L"def") != 0 && !app.IsFileInMemoryTextures(player->customTextureUrl)) +diff --git a/Minecraft.Client/Common/Consoles_App.cpp b/Minecraft.Client/Common/Consoles_App.cpp +index 0a2fd159a4..c98837890d 100644 +--- a/Minecraft.Client/Common/Consoles_App.cpp ++++ b/Minecraft.Client/Common/Consoles_App.cpp +@@ -187,6 +187,7 @@ CMinecraftApp::CMinecraftApp() + #endif + + ZeroMemory(m_playerColours,MINECRAFT_NET_MAX_PLAYERS); ++ ZeroMemory(m_playerMapIcons,MINECRAFT_NET_MAX_PLAYERS); + + m_iDLCOfferC=0; + m_bAllDLCContentRetrieved=true; +@@ -8463,6 +8464,39 @@ short CMinecraftApp::GetPlayerColour(BYTE networkSmallId) + return index; + } + ++void CMinecraftApp::SetPlayerMapIcon(const wchar_t* name, char icon) ++{ ++ if (name == nullptr) return; ++ // Update existing entry or use first empty slot ++ int emptySlot = -1; ++ for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; ++i) ++ { ++ if (m_playerMapIcons[i].name[0] != 0 && _wcsicmp(m_playerMapIcons[i].name, name) == 0) ++ { ++ m_playerMapIcons[i].icon = icon; ++ return; ++ } ++ if (emptySlot < 0 && m_playerMapIcons[i].name[0] == 0) ++ emptySlot = i; ++ } ++ if (emptySlot >= 0) ++ { ++ wcsncpy_s(m_playerMapIcons[emptySlot].name, 32, name, _TRUNCATE); ++ m_playerMapIcons[emptySlot].icon = icon; ++ } ++} ++ ++char CMinecraftApp::GetPlayerMapIconByName(const wchar_t* name) ++{ ++ if (name == nullptr) return 0; ++ for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; ++i) ++ { ++ if (m_playerMapIcons[i].name[0] != 0 && _wcsicmp(m_playerMapIcons[i].name, name) == 0) ++ return m_playerMapIcons[i].icon; ++ } ++ return 0; ++} ++ + + unsigned int CMinecraftApp::GetPlayerPrivileges(BYTE networkSmallId) + { +diff --git a/Minecraft.Client/Common/Consoles_App.h b/Minecraft.Client/Common/Consoles_App.h +index 0c1c261efd..ff7aa8e88d 100644 +--- a/Minecraft.Client/Common/Consoles_App.h ++++ b/Minecraft.Client/Common/Consoles_App.h +@@ -747,10 +747,14 @@ class CMinecraftApp + private: + BYTE m_playerColours[MINECRAFT_NET_MAX_PLAYERS]; // An array of QNet small-id's + unsigned int m_playerGamePrivileges[MINECRAFT_NET_MAX_PLAYERS]; ++ struct PlayerMapIconEntry { wchar_t name[32]; char icon; }; ++ PlayerMapIconEntry m_playerMapIcons[MINECRAFT_NET_MAX_PLAYERS]; + + public: + void UpdatePlayerInfo(BYTE networkSmallId, SHORT playerColourIndex, unsigned int playerGamePrivileges); + short GetPlayerColour(BYTE networkSmallId); ++ void SetPlayerMapIcon(const wchar_t* name, char icon); ++ char GetPlayerMapIconByName(const wchar_t* name); + unsigned int GetPlayerPrivileges(BYTE networkSmallId); + + wstring getEntityName(eINSTANCEOF type); +diff --git a/Minecraft.Client/Common/UI/UIScene_InGameInfoMenu.cpp b/Minecraft.Client/Common/UI/UIScene_InGameInfoMenu.cpp +index b8e1844456..d3a149df59 100644 +--- a/Minecraft.Client/Common/UI/UIScene_InGameInfoMenu.cpp ++++ b/Minecraft.Client/Common/UI/UIScene_InGameInfoMenu.cpp +@@ -512,7 +512,7 @@ UIScene_InGameInfoMenu::PlayerInfo *UIScene_InGameInfoMenu::BuildPlayerInfo(INet + } + + info->m_voiceStatus = voiceStatus; +- info->m_colorState = app.GetPlayerColour(info->m_smallId); ++ info->m_colorState = app.GetPlayerMapIconByName(player->GetOnlineName()); + info->m_name = playerName; + + return info; +diff --git a/Minecraft.Client/Common/UI/UIScene_TeleportMenu.cpp b/Minecraft.Client/Common/UI/UIScene_TeleportMenu.cpp +index 017af93ef6..d8390d3734 100644 +--- a/Minecraft.Client/Common/UI/UIScene_TeleportMenu.cpp ++++ b/Minecraft.Client/Common/UI/UIScene_TeleportMenu.cpp +@@ -193,12 +193,12 @@ void UIScene_TeleportMenu::tick() + { + m_players[i] = player->GetSmallId(); + +- short icon = app.GetPlayerColour( m_players[i] ); ++ short icon = static_cast(app.GetPlayerMapIconByName(player->GetOnlineName())); + + if(icon != m_playersColourState[i]) + { + m_playersColourState[i] = icon; +- m_playerList.setPlayerIcon( i, (int)app.GetPlayerColour( m_players[i] ) ); ++ m_playerList.setPlayerIcon( i, (int)icon ); + } + + wstring playerName = L""; + +From 898d4352a3eedb9788d20f8ec03fcbfa0b192f6b Mon Sep 17 00:00:00 2001 +From: itsRevela +Date: Thu, 26 Mar 2026 23:54:51 -0500 +Subject: [PATCH 3/3] Send AddPlayerPacket for all players on join and + RemoveEntitiesPacket on disconnect + +Players now appear in each other's Tab list immediately on join, +regardless of render distance. Previously, players only appeared when +they entered entity tracking range because AddPlayerPacket was only +sent through the TrackedEntity system. + +On disconnect, a RemoveEntitiesPacket is broadcast to all clients so +players added via the join broadcast are properly cleaned up, not just +those within tracking range. +--- + Minecraft.Client/PlayerList.cpp | 52 +++++++++++++++++++++++++++++++++ + 1 file changed, 52 insertions(+) + +diff --git a/Minecraft.Client/PlayerList.cpp b/Minecraft.Client/PlayerList.cpp +index ba82ec6acd..8a6b531a57 100644 +--- a/Minecraft.Client/PlayerList.cpp ++++ b/Minecraft.Client/PlayerList.cpp +@@ -496,6 +496,50 @@ void PlayerList::add(shared_ptr player) + } + } + ++ // Send AddPlayerPackets so all players appear in each other's Tab list ++ // regardless of render distance. The entity tracking system will send ++ // another AddPlayerPacket when they enter range, which is handled ++ // gracefully by putEntity replacing the old entity. ++ { ++ PlayerUID xuid = INVALID_XUID; ++ PlayerUID onlineXuid = INVALID_XUID; ++#ifndef MINECRAFT_SERVER_BUILD ++ xuid = player->getXuid(); ++ onlineXuid = player->getOnlineXuid(); ++#endif ++ int xp = Mth::floor(player->x * 32.0); ++ int yp = Mth::floor(player->y * 32.0); ++ int zp = Mth::floor(player->z * 32.0); ++ int yRotp = Mth::floor(player->yRot * 256.0f / 360.0f); ++ int xRotp = Mth::floor(player->xRot * 256.0f / 360.0f); ++ int yHeadRotp = Mth::floor(player->yHeadRot * 256.0f / 360.0f); ++ ++ // Broadcast the new player to all existing players ++ broadcastAll(std::make_shared(player, xuid, onlineXuid, xp, yp, zp, yRotp, xRotp, yHeadRotp)); ++ ++ // Send all existing players to the new player ++ for (size_t i = 0; i < players.size(); i++) ++ { ++ shared_ptr op = players.at(i); ++ if (op != player && op->connection->getNetworkPlayer()) ++ { ++ PlayerUID opXuid = INVALID_XUID; ++ PlayerUID opOnlineXuid = INVALID_XUID; ++#ifndef MINECRAFT_SERVER_BUILD ++ opXuid = op->getXuid(); ++ opOnlineXuid = op->getOnlineXuid(); ++#endif ++ int oxp = Mth::floor(op->x * 32.0); ++ int oyp = Mth::floor(op->y * 32.0); ++ int ozp = Mth::floor(op->z * 32.0); ++ int oyRotp = Mth::floor(op->yRot * 256.0f / 360.0f); ++ int oxRotp = Mth::floor(op->xRot * 256.0f / 360.0f); ++ int oyHeadRotp = Mth::floor(op->yHeadRot * 256.0f / 360.0f); ++ player->connection->send(std::make_shared(op, opXuid, opOnlineXuid, oxp, oyp, ozp, oyRotp, oxRotp, oyHeadRotp)); ++ } ++ } ++ } ++ + if(level->isAtLeastOnePlayerSleeping()) + { + shared_ptr firstSleepingPlayer = nullptr; +@@ -528,6 +572,14 @@ if (player->riding != nullptr) + level->removeEntityImmediately(player->riding); + app.DebugPrintf("removing player mount"); + } ++ // Notify all clients to remove this player entity, not just those who ++ // had the player in tracking range. This ensures players added to the ++ // Tab list via the AddPlayerPacket broadcast are properly cleaned up. ++ { ++ intArray ids(1); ++ ids[0] = player->entityId; ++ broadcastAll(std::make_shared(ids)); ++ } + level->getTracker()->removeEntity(player); + level->removeEntity(player); + level->getChunkMap()->remove(player); diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index b93b91d7..578549f8 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -65,6 +65,29 @@ #include "../Minecraft.World/DurangoStats.h" #include "../Minecraft.World/GenericStats.h" #endif +namespace +{ + char mapIconToFrame(char iconSlot) + { + if (iconSlot >= 8) return iconSlot - 4; + return iconSlot; + } + + // Same hash as getRandomPlayerMapIcon in MapItemSavedData.cpp, returning + // the Iggy/SWF frame index (0-7) instead of the raw icon slot. + char computePlayerMapFrame(int entityId, int playerIndex) + { + static const char PLAYER_MAP_ICON_SLOTS[] = { 0, 1, 2, 3, 8, 9, 10, 11 }; + unsigned int seed = static_cast(entityId); + seed ^= static_cast(playerIndex * 0x9E3779B9u); + seed ^= (seed >> 16); + seed *= 0x7FEB352Du; + seed ^= (seed >> 15); + seed *= 0x846CA68Bu; + seed ^= (seed >> 16); + return mapIconToFrame(PLAYER_MAP_ICON_SLOTS[seed % 8]); + } +} ClientConnection::ClientConnection(Minecraft *minecraft, const wstring& ip, int port) { @@ -377,6 +400,7 @@ void ClientConnection::handleLogin(shared_ptr packet) BYTE networkSmallId = getSocket()->getSmallId(); app.UpdatePlayerInfo(networkSmallId, packet->m_playerIndex, packet->m_uiGamePrivileges); + app.SetPlayerMapIcon(minecraft->player->getName().c_str(), computePlayerMapFrame(packet->clientVersion, packet->m_playerIndex)); minecraft->player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_All, packet->m_uiGamePrivileges); // Assume all privileges are on, so that the first message we see only indicates things that have been turned off @@ -447,6 +471,7 @@ void ClientConnection::handleLogin(shared_ptr packet) BYTE networkSmallId = getSocket()->getSmallId(); app.UpdatePlayerInfo(networkSmallId, packet->m_playerIndex, packet->m_uiGamePrivileges); + app.SetPlayerMapIcon(player->getName().c_str(), computePlayerMapFrame(packet->clientVersion, packet->m_playerIndex)); player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_All, packet->m_uiGamePrivileges); // Assume all privileges are on, so that the first message we see only indicates things that have been turned off @@ -976,6 +1001,7 @@ void ClientConnection::handleAddPlayer(shared_ptr packet) player->setPlayerIndex( packet->m_playerIndex ); player->setCustomSkin( packet->m_skinId ); player->setCustomCape( packet->m_capeId ); + app.SetPlayerMapIcon(packet->name.c_str(), computePlayerMapFrame(packet->id, packet->m_playerIndex)); player->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_All, packet->m_uiGamePrivileges); if (!player->customTextureUrl.empty() && player->customTextureUrl.substr(0, 3).compare(L"def") != 0 && !app.IsFileInMemoryTextures(player->customTextureUrl)) diff --git a/Minecraft.Client/Common/Consoles_App.cpp b/Minecraft.Client/Common/Consoles_App.cpp index 0e8a0163..694d4f50 100644 --- a/Minecraft.Client/Common/Consoles_App.cpp +++ b/Minecraft.Client/Common/Consoles_App.cpp @@ -187,6 +187,7 @@ CMinecraftApp::CMinecraftApp() #endif ZeroMemory(m_playerColours,MINECRAFT_NET_MAX_PLAYERS); + ZeroMemory(m_playerMapIcons,MINECRAFT_NET_MAX_PLAYERS); m_iDLCOfferC=0; m_bAllDLCContentRetrieved=true; @@ -8463,6 +8464,39 @@ short CMinecraftApp::GetPlayerColour(BYTE networkSmallId) return index; } +void CMinecraftApp::SetPlayerMapIcon(const wchar_t* name, char icon) +{ + if (name == nullptr) return; + // Update existing entry or use first empty slot + int emptySlot = -1; + for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; ++i) + { + if (m_playerMapIcons[i].name[0] != 0 && _wcsicmp(m_playerMapIcons[i].name, name) == 0) + { + m_playerMapIcons[i].icon = icon; + return; + } + if (emptySlot < 0 && m_playerMapIcons[i].name[0] == 0) + emptySlot = i; + } + if (emptySlot >= 0) + { + wcsncpy_s(m_playerMapIcons[emptySlot].name, 32, name, _TRUNCATE); + m_playerMapIcons[emptySlot].icon = icon; + } +} + +char CMinecraftApp::GetPlayerMapIconByName(const wchar_t* name) +{ + if (name == nullptr) return 0; + for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; ++i) + { + if (m_playerMapIcons[i].name[0] != 0 && _wcsicmp(m_playerMapIcons[i].name, name) == 0) + return m_playerMapIcons[i].icon; + } + return 0; +} + unsigned int CMinecraftApp::GetPlayerPrivileges(BYTE networkSmallId) { diff --git a/Minecraft.Client/Common/Consoles_App.h b/Minecraft.Client/Common/Consoles_App.h index b43b4f63..bd294b46 100644 --- a/Minecraft.Client/Common/Consoles_App.h +++ b/Minecraft.Client/Common/Consoles_App.h @@ -747,10 +747,14 @@ public: private: BYTE m_playerColours[MINECRAFT_NET_MAX_PLAYERS]; // An array of QNet small-id's unsigned int m_playerGamePrivileges[MINECRAFT_NET_MAX_PLAYERS]; + struct PlayerMapIconEntry { wchar_t name[32]; char icon; }; + PlayerMapIconEntry m_playerMapIcons[MINECRAFT_NET_MAX_PLAYERS]; public: void UpdatePlayerInfo(BYTE networkSmallId, SHORT playerColourIndex, unsigned int playerGamePrivileges); short GetPlayerColour(BYTE networkSmallId); + void SetPlayerMapIcon(const wchar_t* name, char icon); + char GetPlayerMapIconByName(const wchar_t* name); unsigned int GetPlayerPrivileges(BYTE networkSmallId); wstring getEntityName(eINSTANCEOF type); diff --git a/Minecraft.Client/Common/UI/UIScene_InGameInfoMenu.cpp b/Minecraft.Client/Common/UI/UIScene_InGameInfoMenu.cpp index b7bc9850..9467889b 100644 --- a/Minecraft.Client/Common/UI/UIScene_InGameInfoMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_InGameInfoMenu.cpp @@ -512,7 +512,7 @@ UIScene_InGameInfoMenu::PlayerInfo *UIScene_InGameInfoMenu::BuildPlayerInfo(INet } info->m_voiceStatus = voiceStatus; - info->m_colorState = app.GetPlayerColour(info->m_smallId); + info->m_colorState = app.GetPlayerMapIconByName(player->GetOnlineName()); info->m_name = playerName; return info; diff --git a/Minecraft.Client/Common/UI/UIScene_TeleportMenu.cpp b/Minecraft.Client/Common/UI/UIScene_TeleportMenu.cpp index 1a021871..7544106b 100644 --- a/Minecraft.Client/Common/UI/UIScene_TeleportMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_TeleportMenu.cpp @@ -193,12 +193,12 @@ void UIScene_TeleportMenu::tick() { m_players[i] = player->GetSmallId(); - short icon = app.GetPlayerColour( m_players[i] ); + short icon = static_cast(app.GetPlayerMapIconByName(player->GetOnlineName())); if(icon != m_playersColourState[i]) { m_playersColourState[i] = icon; - m_playerList.setPlayerIcon( i, (int)app.GetPlayerColour( m_players[i] ) ); + m_playerList.setPlayerIcon( i, (int)icon ); } wstring playerName = L"";