Skip to content

Color PlayerList names from nametags#47

Open
ukiyo-dev wants to merge 1 commit into
LatiteClient:masterfrom
ukiyo-dev:pr/tablist-name-colors
Open

Color PlayerList names from nametags#47
ukiyo-dev wants to merge 1 commit into
LatiteClient:masterfrom
ukiyo-dev:pr/tablist-name-colors

Conversation

@ukiyo-dev
Copy link
Copy Markdown

Summary

This updates the PlayerList/TabList HUD module to render player names with Minecraft nametag color formatting.

NameTag formatting is collected from SetEntityData packets and cached by runtime actor ID. During TickEvent, the PlayerList matches cached
nametags back to current player actors and stores formatted row names for rendering.

Changes

  • Add minimal SetActorDataPacket support for extracting NameTag metadata
  • Add NameTagCache for packet-derived runtime ID to nametag mapping
  • Update PlayerList rows from cached nametag data during TickEvent
  • Render Minecraft section sign color codes in the PlayerList
  • Sort PlayerList rows by color, then visible player name
  • Add defensive setting type recovery for PlayerList config values

Testing

I have tested it in CubeCraft by using nightly build

Copilot AI review requested due to automatic review settings May 24, 2026 15:19
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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::tryGetNameTag to extract runtimeId + nameTag from packet metadata.
  • Adds NameTagCache to store name tags observed via network packets and retain/clear them across sessions/dimension changes.
  • Enhances TabList to 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.

Comment thread src/mc/common/network/packet/SetActorDataPacket.h
Comment thread src/mc/common/network/packet/SetActorDataPacket.cpp
Comment thread src/client/misc/NameTagCache.cpp
Comment thread src/client/feature/module/modules/hud/TabList.cpp
Comment thread src/client/feature/module/modules/hud/TabList.cpp
Comment thread src/mc/common/network/Packet.h
@ukiyo-dev
Copy link
Copy Markdown
Author

917a14ef4e2a1faa1355530462de1759

Copy link
Copy Markdown
Member

@Plextora Plextora left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the rationale behind integrating this function?

Comment on lines +212 to +213
listen<RenderNameTagEvent>(static_cast<EventListenerFunc>(&TabList::onRenderNameTag), true);
listen<TickEvent>(static_cast<EventListenerFunc>(&TabList::onTick), true);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +259 to +265
auto nameTag = NameTagCache::getNetworkNameTag(runtimeId);
if (!nameTag) continue;

std::string rowName = colorizedPlayerName(player->playerName, *nameTag);
if (hasFormatCode(rowName)) {
coloredNameCache[player->playerName] = rowName;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +227 to +232
for (auto& ent : *level->getPlayerList()) {
std::string rowName = colorizedPlayerName(ent.second.name, *tag);
if (hasFormatCode(rowName)) {
coloredNameCache[ent.second.name] = rowName;
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +7 to +34
__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) {
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants