Color PlayerList names from nametags#47
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds support for capturing player name tag metadata from SetActorData network packets and using it to colorize/sort the in-game tab list.
Changes:
- Introduces
SetActorDataPacket::tryGetNameTagto extract runtimeId + nameTag from packet metadata. - Adds
NameTagCacheto store name tags observed via network packets and retain/clear them across sessions/dimension changes. - Enhances
TabListto render formatted (Minecraft §-code) names, add a text size setting, and keep a colored-name cache updated via tick + name tag render events.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/mc/common/network/packet/SetActorDataPacket.h | New packet type API for reading a name tag from SetActorData metadata |
| src/mc/common/network/packet/SetActorDataPacket.cpp | Implements extraction logic using raw packet layout + metadata vtable calls |
| src/mc/common/network/Packet.h | Adds SET_ENTITY_DATA packet ID |
| src/client/misc/NameTagCache.h | Declares a shared cache API for network-derived name tags |
| src/client/misc/NameTagCache.cpp | Implements the cache with a global unordered_map |
| src/client/memory/hook/hooks/PacketHooks.cpp | Records/clears network name tags based on packet types |
| src/client/feature/module/modules/hud/TabList.h | Adds new event handlers, colored-name cache, and a text size setting |
| src/client/feature/module/modules/hud/TabList.cpp | Implements formatted name rendering, updates colored names on tick/name-tag render, and sorts tab rows |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Plextora
left a comment
There was a problem hiding this comment.
Nice feature idea and PR. I left some comments for things that will need to be addressed before this is mergable.
|
|
||
| void TabList::onRenderOverlay(Event& evG) { | ||
| auto& ev = static_cast<RenderOverlayEvent&>(evG); | ||
| (void)evG; |
There was a problem hiding this comment.
Why not just remove the variable if you're going to cast it to void? If you want to silence compiler warnings add a comment that does it.
| flush(); | ||
| } | ||
|
|
||
| ColorValue colorOrDefault(ValueType const& value, ColorValue const& fallback) { |
There was a problem hiding this comment.
What is the rationale behind integrating this function?
| listen<RenderNameTagEvent>(static_cast<EventListenerFunc>(&TabList::onRenderNameTag), true); | ||
| listen<TickEvent>(static_cast<EventListenerFunc>(&TabList::onTick), true); |
There was a problem hiding this comment.
Why do these listeners run with callWhenInactive = true? If the cache must be maintained globally, that seems like service-level state rather than module event work, otherwise these events should just follow the normal module listener path and only run while enabled.
| auto nameTag = NameTagCache::getNetworkNameTag(runtimeId); | ||
| if (!nameTag) continue; | ||
|
|
||
| std::string rowName = colorizedPlayerName(player->playerName, *nameTag); | ||
| if (hasFormatCode(rowName)) { | ||
| coloredNameCache[player->playerName] = rowName; | ||
| } |
There was a problem hiding this comment.
This leaves stale formatted names in coloredNameCache. If a player's network nametag disappears, becomes empty, or changes back to an unformatted name, this code just skips the update and keeps rendering the previous color until the player leaves. We should erase/update the per-player cache when getNetworkNameTag is missing or colorizedPlayerName has no format codes.
| for (auto& ent : *level->getPlayerList()) { | ||
| std::string rowName = colorizedPlayerName(ent.second.name, *tag); | ||
| if (hasFormatCode(rowName)) { | ||
| coloredNameCache[ent.second.name] = rowName; | ||
| } | ||
| } |
There was a problem hiding this comment.
This fallback matches every rendered nametag string against every player-list name, so substring collisions can color the wrong row (Alex matching Alexis, names appearing in prefixes/suffixes, etc.). Since RenderNameTagEvent only exposes text and not the actor, this the best source for player identity. I'd be nice if you could compare against actor/runtime IDs (maybe even xuids?) to make this more consistent, if they're unique that way. Therefore, I'd avoid using this path for cache population unless the event can carry such kind of IDs.
| __try { | ||
| constexpr uint16_t nameTagMetadataId = 4; | ||
| constexpr uint8_t stringMetadataType = 4; | ||
|
|
||
| auto packetBase = reinterpret_cast<uintptr_t>(this); | ||
| const auto packetRuntimeId = *reinterpret_cast<const uint64_t*>(packetBase + 0x30); | ||
| auto* metadataBegin = *reinterpret_cast<void***>(packetBase + 0x38); | ||
| auto* metadataEnd = *reinterpret_cast<void***>(packetBase + 0x40); | ||
| if (!metadataBegin || !metadataEnd || metadataEnd < metadataBegin) return false; | ||
|
|
||
| for (auto* it = metadataBegin; it != metadataEnd; ++it) { | ||
| auto* metadata = *it; | ||
| if (!metadata) continue; | ||
|
|
||
| auto** vtable = *reinterpret_cast<void***>(metadata); | ||
| if (!vtable) continue; | ||
|
|
||
| const auto metadataId = reinterpret_cast<uint16_t(__fastcall*)(void*)>(vtable[1])(metadata); | ||
| const auto metadataType = reinterpret_cast<uint8_t(__fastcall*)(void*)>(vtable[2])(metadata); | ||
| if (metadataId != nameTagMetadataId || metadataType != stringMetadataType) continue; | ||
|
|
||
| *runtimeId = packetRuntimeId; | ||
| *nameTag = *reinterpret_cast<std::string*>(reinterpret_cast<uintptr_t>(metadata) + 0x10); | ||
| return true; | ||
| } | ||
| } | ||
| __except (EXCEPTION_EXECUTE_HANDLER) { | ||
| } |
There was a problem hiding this comment.
This __try/__except with an empty __except case block silently eats access violations from the packet layout probing and converts them into false, which hides offset/layout bugs from Latite’s crash reporting. If the memory layout is incorrect after a Minecraft update, this will not properly show up in a crash report. Please remove the __try/__except block.

Summary
This updates the PlayerList/TabList HUD module to render player names with Minecraft nametag color formatting.
NameTag formatting is collected from
SetEntityDatapackets and cached by runtime actor ID. DuringTickEvent, the PlayerList matches cachednametags back to current player actors and stores formatted row names for rendering.
Changes
SetActorDataPacketsupport for extracting NameTag metadataNameTagCachefor packet-derived runtime ID to nametag mappingTickEventTesting
I have tested it in CubeCraft by using nightly build