This commit is contained in:
Revela 2026-04-21 20:03:58 -04:00 committed by GitHub
commit 2015e0c6f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 187 additions and 20 deletions

View file

@ -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<unsigned int>(entityId);
seed ^= static_cast<unsigned int>(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<LoginPacket> 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<LoginPacket> 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
@ -917,6 +943,36 @@ void ClientConnection::handleAddPlayer(shared_ptr<AddPlayerPacket> 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<BYTE>(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
@ -946,6 +1002,7 @@ void ClientConnection::handleAddPlayer(shared_ptr<AddPlayerPacket> 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))
@ -1088,28 +1145,27 @@ void ClientConnection::handleRemoveEntity(shared_ptr<RemoveEntitiesPacket> packe
for (int i = 0; i < packet->ids.length; i++)
{
shared_ptr<Entity> entity = getEntity(packet->ids[i]);
if (entity != nullptr && entity->GetType() == eTYPE_PLAYER)
if (entity != nullptr)
{
shared_ptr<Player> player = dynamic_pointer_cast<Player>(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;
}
}
}

View file

@ -188,6 +188,7 @@ CMinecraftApp::CMinecraftApp()
#endif
ZeroMemory(m_playerColours,MINECRAFT_NET_MAX_PLAYERS);
ZeroMemory(m_playerMapIcons,MINECRAFT_NET_MAX_PLAYERS);
m_iDLCOfferC=0;
m_bAllDLCContentRetrieved=true;
@ -8555,6 +8556,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)
{

View file

@ -749,10 +749,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);

View file

@ -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("<UIScene_InGameInfoMenu::OnPlayerChanged> 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);
@ -491,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;

View file

@ -193,12 +193,12 @@ void UIScene_TeleportMenu::tick()
{
m_players[i] = player->GetSmallId();
short icon = app.GetPlayerColour( m_players[i] );
short icon = static_cast<short>(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"";

View file

@ -496,6 +496,50 @@ void PlayerList::add(shared_ptr<ServerPlayer> 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<AddPlayerPacket>(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<ServerPlayer> 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<AddPlayerPacket>(op, opXuid, opOnlineXuid, oxp, oyp, ozp, oyRotp, oxRotp, oyHeadRotp));
}
}
}
if(level->isAtLeastOnePlayerSleeping())
{
shared_ptr<ServerPlayer> 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<RemoveEntitiesPacket>(ids));
}
level->getTracker()->removeEntity(player);
level->removeEntity(player);
level->getChunkMap()->remove(player);