From 13e8ca8263f65e45f8c86de8103830d6595d8499 Mon Sep 17 00:00:00 2001 From: FuSheng <36793077+Ukiyo-dev@users.noreply.github.com> Date: Sun, 24 May 2026 21:37:36 +0800 Subject: [PATCH 1/2] Color PlayerList names from nametags --- .../feature/module/modules/hud/TabList.cpp | 309 +++++++++++++++++- .../feature/module/modules/hud/TabList.h | 6 + src/client/memory/hook/hooks/PacketHooks.cpp | 11 + src/client/misc/NameTagCache.cpp | 36 ++ src/client/misc/NameTagCache.h | 13 + src/mc/common/network/Packet.h | 3 +- .../network/packet/SetActorDataPacket.cpp | 37 +++ .../network/packet/SetActorDataPacket.h | 9 + 8 files changed, 409 insertions(+), 15 deletions(-) create mode 100644 src/client/misc/NameTagCache.cpp create mode 100644 src/client/misc/NameTagCache.h create mode 100644 src/mc/common/network/packet/SetActorDataPacket.cpp create mode 100644 src/mc/common/network/packet/SetActorDataPacket.h diff --git a/src/client/feature/module/modules/hud/TabList.cpp b/src/client/feature/module/modules/hud/TabList.cpp index be22ca44..61340c28 100644 --- a/src/client/feature/module/modules/hud/TabList.cpp +++ b/src/client/feature/module/modules/hud/TabList.cpp @@ -1,7 +1,204 @@ #include "pch.h" #include "TabList.h" #include "client/Latite.h" +#include "client/event/events/RenderNameTagEvent.h" +#include "client/event/events/TickEvent.h" +#include "client/misc/NameTagCache.h" #include "../../../../render/asset/Assets.h" +#include "mc/common/world/actor/player/Player.h" +#include "util/Logger.h" +#include +#include +#include +#include + +namespace { + bool readFormatCode(std::string const& text, size_t index, char& code, size_t& codeSize) { + if (index + 1 < text.size() && static_cast(text[index]) == 0xA7) { + code = text[index + 1]; + codeSize = 2; + return true; + } + if (index + 2 < text.size() + && static_cast(text[index]) == 0xC2 + && static_cast(text[index + 1]) == 0xA7) { + code = text[index + 2]; + codeSize = 3; + return true; + } + return false; + } + + bool isColorCode(char code) { + return std::isxdigit(static_cast(code)); + } + + d2d::Color minecraftColor(char code, d2d::Color const& fallback) { + switch (static_cast(std::tolower(static_cast(code)))) { + case '0': return d2d::Color::RGB(0, 0, 0); + case '1': return d2d::Color::RGB(0, 0, 170); + case '2': return d2d::Color::RGB(0, 170, 0); + case '3': return d2d::Color::RGB(0, 170, 170); + case '4': return d2d::Color::RGB(170, 0, 0); + case '5': return d2d::Color::RGB(170, 0, 170); + case '6': return d2d::Color::RGB(255, 170, 0); + case '7': return d2d::Color::RGB(170, 170, 170); + case '8': return d2d::Color::RGB(85, 85, 85); + case '9': return d2d::Color::RGB(85, 85, 255); + case 'a': return d2d::Color::RGB(85, 255, 85); + case 'b': return d2d::Color::RGB(85, 255, 255); + case 'c': return d2d::Color::RGB(255, 85, 85); + case 'd': return d2d::Color::RGB(255, 85, 255); + case 'e': return d2d::Color::RGB(255, 255, 85); + case 'f': return d2d::Color::RGB(255, 255, 255); + case 'r': return fallback; + default: return fallback; + } + } + + std::string stripFormatCodes(std::string const& text) { + std::string stripped; + stripped.reserve(text.size()); + for (size_t i = 0; i < text.size();) { + char code = 0; + size_t codeSize = 0; + if (readFormatCode(text, i, code, codeSize)) { + i += codeSize; + continue; + } + stripped += text[i++]; + } + return stripped; + } + + bool hasFormatCode(std::string const& text) { + for (size_t i = 0; i < text.size();) { + char code = 0; + size_t codeSize = 0; + if (readFormatCode(text, i, code, codeSize)) return true; + i++; + } + return false; + } + + int colorCodeIndex(char code) { + if (code >= '0' && code <= '9') return code - '0'; + if (code >= 'a' && code <= 'f') return code - 'a' + 10; + if (code >= 'A' && code <= 'F') return code - 'A' + 10; + return 16; + } + + int firstColorSortIndex(std::string const& text) { + for (size_t i = 0; i < text.size();) { + char code = 0; + size_t codeSize = 0; + if (readFormatCode(text, i, code, codeSize)) { + if (isColorCode(code)) return colorCodeIndex(code); + i += codeSize; + continue; + } + i++; + } + return 16; + } + + std::string tabRowName(SDK::PlayerListEntry& entry, std::unordered_map const& coloredNameCache) { + if (auto it = coloredNameCache.find(entry.name); it != coloredNameCache.end()) return it->second; + return entry.name; + } + + std::vector sortedPlayerListRows(SDK::Level* level, + std::unordered_map const& coloredNameCache) { + std::vector rows; + if (!level || !level->getPlayerList()) return rows; + + rows.reserve(level->getPlayerList()->size()); + for (auto& ent : *level->getPlayerList()) { + rows.push_back(&ent.second); + } + + std::stable_sort(rows.begin(), rows.end(), [&](auto* a, auto* b) { + std::string aName = tabRowName(*a, coloredNameCache); + std::string bName = tabRowName(*b, coloredNameCache); + const int aColor = firstColorSortIndex(aName); + const int bColor = firstColorSortIndex(bName); + if (aColor != bColor) return aColor < bColor; + return stripFormatCodes(aName) < stripFormatCodes(bName); + }); + + return rows; + } + + std::string colorizedPlayerName(std::string const& playerName, std::string const& nameTag) { + const size_t namePos = stripFormatCodes(nameTag).find(playerName); + if (namePos == std::string::npos) return playerName; + const size_t nameEnd = namePos + playerName.size(); + + std::string activeColor; + std::string formattedName; + size_t visiblePos = 0; + for (size_t i = 0; i < nameTag.size();) { + char code = 0; + size_t codeSize = 0; + if (readFormatCode(nameTag, i, code, codeSize)) { + if (isColorCode(code) || code == 'r' || code == 'R') { + activeColor.assign(nameTag, i, codeSize); + } + if (visiblePos >= namePos && visiblePos < nameEnd) { + formattedName.append(nameTag, i, codeSize); + } + i += codeSize; + continue; + } + if (visiblePos >= nameEnd) break; + if (visiblePos >= namePos) { + if (formattedName.empty()) formattedName += activeColor; + formattedName += nameTag[i]; + } + visiblePos++; + i++; + } + return formattedName.empty() ? playerName : formattedName; + } + + void drawFormattedText(D2DUtil& dc, d2d::Rect const& rc, std::string const& text, d2d::Color const& fallbackColor, + Renderer::FontSelection font, float textSize) { + float x = rc.left; + d2d::Color color = fallbackColor; + std::string segment; + auto flush = [&]() { + if (segment.empty()) return; + std::wstring wide = util::StrToWStr(segment); + dc.drawText({ x, rc.top, rc.right, rc.bottom }, wide, color, font, textSize, + DWRITE_TEXT_ALIGNMENT_LEADING, DWRITE_PARAGRAPH_ALIGNMENT_NEAR, false); + x += dc.getTextSize(wide, font, textSize, true, false).x; + segment.clear(); + }; + + for (size_t i = 0; i < text.size();) { + char code = 0; + size_t codeSize = 0; + if (readFormatCode(text, i, code, codeSize)) { + flush(); + color = minecraftColor(code, fallbackColor).asAlpha(fallbackColor.a); + i += codeSize; + continue; + } + segment += text[i++]; + } + flush(); + } + + ColorValue colorOrDefault(ValueType const& value, ColorValue const& fallback) { + if (!std::holds_alternative(value)) return fallback; + return std::get(value); + } + + float floatOrDefault(ValueType const& value, float fallback) { + if (!std::holds_alternative(value)) return fallback; + return std::get(value).value; + } +} TabList::TabList() : Module("PlayerList", LocalizeString::get("client.module.tabList.name"), LocalizeString::get("client.module.tabList.desc"), HUD, VK_TAB) { @@ -9,22 +206,103 @@ TabList::TabList() : Module("PlayerList", LocalizeString::get("client.module.tab LocalizeString::get("client.module.tabList.textColor.desc"), textCol); addSetting("bgColor", LocalizeString::get("client.module.tabList.bgColor.name"), LocalizeString::get("client.module.tabList.bgColor.desc"), bgCol); + addSliderSetting("textSize", LocalizeString::get("client.textmodule.props.textSize.name"), L"", textSizeS, + FloatValue(2.f), FloatValue(100.f), FloatValue(2.f)); listen(static_cast(&TabList::onRenderOverlay)); + listen(static_cast(&TabList::onRenderNameTag), true); + listen(static_cast(&TabList::onTick), true); +} + +void TabList::onRenderNameTag(Event& evG) { + auto& ev = static_cast(evG); + auto* tag = ev.getNametag(); + if (!tag || !hasFormatCode(*tag)) return; + + auto* client = SDK::ClientInstance::get(); + if (!client || !client->minecraft) return; + + auto* level = client->minecraft->getLevel(); + if (!level || !level->getPlayerList()) return; + + for (auto& ent : *level->getPlayerList()) { + std::string rowName = colorizedPlayerName(ent.second.name, *tag); + if (hasFormatCode(rowName)) { + coloredNameCache[ent.second.name] = rowName; + } + } +} + +void TabList::onTick(Event& evG) { + auto& ev = static_cast(evG); + auto* level = ev.getLevel(); + if (!level || !level->getPlayerList()) { + coloredNameCache.clear(); + return; + } + + std::unordered_set activePlayers; + activePlayers.reserve(level->getPlayerList()->size()); + for (auto& ent : *level->getPlayerList()) { + activePlayers.insert(ent.second.name); + } + + std::unordered_set activeRuntimeIds; + for (auto* actor : level->getRuntimeActorList()) { + if (!actor || !actor->isPlayer()) continue; + + auto runtimeId = actor->getRuntimeID(); + activeRuntimeIds.insert(runtimeId); + + auto* player = static_cast(actor); + if (!activePlayers.contains(player->playerName)) continue; + + auto nameTag = NameTagCache::getNetworkNameTag(runtimeId); + if (!nameTag) continue; + + std::string rowName = colorizedPlayerName(player->playerName, *nameTag); + if (hasFormatCode(rowName)) { + coloredNameCache[player->playerName] = rowName; + } + } + NameTagCache::retainNetworkNameTags(activeRuntimeIds); + + for (auto it = coloredNameCache.begin(); it != coloredNameCache.end();) { + if (!activePlayers.contains(it->first)) { + it = coloredNameCache.erase(it); + continue; + } + ++it; + } +} + +void TabList::afterLoadConfig() { + if (!std::holds_alternative(textCol)) { + Logger::Warn("TabList: textColor setting was invalid, restoring default"); + textCol = ColorValue(1.f, 1.f, 1.f, 1.f); + } + if (!std::holds_alternative(bgCol)) { + Logger::Warn("TabList: bgColor setting was invalid, restoring default"); + bgCol = ColorValue(0.f, 0.f, 0.f, 0.5f); + } + if (!std::holds_alternative(textSizeS)) { + Logger::Warn("TabList: textSize setting was invalid, restoring default"); + textSizeS = FloatValue(20.f); + } } void TabList::onRenderOverlay(Event& evG) { - auto& ev = static_cast(evG); + (void)evG; auto plr = SDK::ClientInstance::get()->getLocalPlayer(); if (!plr) return; D2DUtil dc; auto lvl = SDK::ClientInstance::get()->minecraft->getLevel(); - auto name = lvl->getLevelName(); + if (!lvl || !lvl->getPlayerList()) return; size_t size = lvl->getPlayerList()->size(); - float textP = 20.f; + float textP = floatOrDefault(textSizeS, 20.f); std::wstring txt; if (SDK::RakNetConnector::get() && SDK::RakNetConnector::get()->featuredServer.size() > 0) { @@ -35,17 +313,20 @@ void TabList::onRenderOverlay(Event& evG) { } constexpr auto font = Renderer::FontSelection::PrimaryRegular; + const ColorValue textColor = colorOrDefault(textCol, ColorValue(1.f, 1.f, 1.f, 1.f)); + const ColorValue backgroundColor = colorOrDefault(bgCol, ColorValue(0.f, 0.f, 0.f, 0.5f)); float sectionHeight = textP * 1.3f; float logoSize = sectionHeight; float logoPad = 4.f; float longestText = dc.getTextSize(txt, font, textP).x; - for (auto& ent : *lvl->getPlayerList()) { - auto w = dc.getTextSize(util::StrToWStr(ent.second.name), font, textP).x + 3.f; - auto const& name = util::StrToWStr(ent.second.name); + auto sortedRows = sortedPlayerListRows(lvl, coloredNameCache); + for (auto* row : sortedRows) { + std::string rowName = tabRowName(*row, coloredNameCache); + auto w = dc.getTextSize(util::StrToWStr(stripFormatCodes(rowName)), font, textP).x + 3.f; for (auto& user : Latite::get().getLatiteUsers()) { - if (user == ent.second.name) { + if (user == row->name) { w += logoPad + logoSize; } } @@ -76,17 +357,17 @@ void TabList::onRenderOverlay(Event& evG) { dc.ctx->SetTransform(mat * D2D1::Matrix3x2F::Translation(ss.x / 2.f - calcWidth / 2.f, 20.f)); - dc.fillRectangle({ 0.f, 0.f, calcWidth, calcHeight + oY }, std::get(this->bgCol).getMainColor()); - dc.drawRectangle({ 0.f, 0.f, calcWidth, calcHeight + oY }, d2d::Color(std::get(this->bgCol).getMainColor()).asAlpha(1.f), 2.f); + dc.fillRectangle({ 0.f, 0.f, calcWidth, calcHeight + oY }, backgroundColor.getMainColor()); + dc.drawRectangle({ 0.f, 0.f, calcWidth, calcHeight + oY }, d2d::Color(backgroundColor.getMainColor()).asAlpha(1.f), 2.f); - dc.drawText({ 0.f, 0.f, calcWidth, oY }, txt, std::get(this->textCol).getMainColor(), font, textP, DWRITE_TEXT_ALIGNMENT_CENTER); + dc.drawText({ 0.f, 0.f, calcWidth, oY }, txt, textColor.getMainColor(), font, textP, DWRITE_TEXT_ALIGNMENT_CENTER); - for (auto& ent : *lvl->getPlayerList()) { - auto const& name = util::StrToWStr(ent.second.name); + for (auto* row : sortedRows) { + std::string rowName = tabRowName(*row, coloredNameCache); d2d::Rect rc = { x, y, x + longestText, y + sectionHeight }; for (auto& user : Latite::get().getLatiteUsers()) { - if (user == ent.second.name) { + if (user == row->name) { rc.left += logoSize + logoPad; d2d::Rect logoRc = { x, y, x + logoSize, y + logoSize }; dc.ctx->DrawBitmap(Latite::getAssets().logoWhite.getBitmap(), logoRc); @@ -96,7 +377,7 @@ void TabList::onRenderOverlay(Event& evG) { // render //dc.drawRectangle(rc, d2d::Colors::BLACK, 0.5f); - dc.drawText(rc, name, std::get(textCol).getMainColor(), font, textP, DWRITE_TEXT_ALIGNMENT_LEADING, DWRITE_PARAGRAPH_ALIGNMENT_NEAR, false); + drawFormattedText(dc, rc, rowName, textColor.getMainColor(), font, textP); idx++; if (idx < maxPerTab) { diff --git a/src/client/feature/module/modules/hud/TabList.h b/src/client/feature/module/modules/hud/TabList.h index 620b5a92..d33df891 100644 --- a/src/client/feature/module/modules/hud/TabList.h +++ b/src/client/feature/module/modules/hud/TabList.h @@ -1,13 +1,19 @@ #pragma once #include "../../HUDModule.h" +#include class TabList : public Module { public: TabList(); void onRenderOverlay(Event&); + void onRenderNameTag(Event&); + void onTick(Event&); + void afterLoadConfig() override; bool shouldHoldToToggle() override { return true; } private: + std::unordered_map coloredNameCache; + ValueType textSizeS = FloatValue(20.f); ValueType textCol = ColorValue(1.f, 1.f, 1.f, 1.f); ValueType bgCol = ColorValue(0.f, 0.f, 0.f, 0.5f); }; diff --git a/src/client/memory/hook/hooks/PacketHooks.cpp b/src/client/memory/hook/hooks/PacketHooks.cpp index 5ff8f557..ce73cb5e 100644 --- a/src/client/memory/hook/hooks/PacketHooks.cpp +++ b/src/client/memory/hook/hooks/PacketHooks.cpp @@ -1,7 +1,9 @@ #include "pch.h" #include "PacketHooks.h" +#include "client/misc/NameTagCache.h" #include "client/script/PluginManager.h" #include +#include namespace { std::shared_ptr SetTitlePacketRead; @@ -38,9 +40,18 @@ void PacketHooks::PacketHandlerDispatcherInstance_handle(void* instance, void* n auto packetId = packet->getID(); if (packetId == SDK::PacketID::CHANGE_DIMENSION) { + NameTagCache::clearNetworkNameTags(); PluginManager::Event sEv{ L"change-dimension", {}, false }; Latite::getPluginManager().dispatchEvent(sEv); } + else if (packetId == SDK::PacketID::SET_ENTITY_DATA) { + auto* setActorData = static_cast(packet.get()); + uint64_t runtimeId = 0; + std::string nameTag; + if (setActorData->tryGetNameTag(&runtimeId, &nameTag)) { + NameTagCache::recordNetworkNameTag(runtimeId, nameTag); + } + } else if (packetId == SDK::PacketID::SET_SCORE) { auto pkt = std::static_pointer_cast(packet); diff --git a/src/client/misc/NameTagCache.cpp b/src/client/misc/NameTagCache.cpp new file mode 100644 index 00000000..3ae45e7b --- /dev/null +++ b/src/client/misc/NameTagCache.cpp @@ -0,0 +1,36 @@ +#include "pch.h" +#include "NameTagCache.h" +#include + +namespace { + std::unordered_map networkNameTags; +} + +void NameTagCache::recordNetworkNameTag(uint64_t runtimeId, std::string const& nameTag) { + if (runtimeId == 0) return; + if (nameTag.empty()) { + networkNameTags.erase(runtimeId); + return; + } + networkNameTags[runtimeId] = nameTag; +} + +std::optional NameTagCache::getNetworkNameTag(uint64_t runtimeId) { + auto it = networkNameTags.find(runtimeId); + if (it == networkNameTags.end() || it->second.empty()) return std::nullopt; + return it->second; +} + +void NameTagCache::retainNetworkNameTags(std::unordered_set const& runtimeIds) { + for (auto it = networkNameTags.begin(); it != networkNameTags.end();) { + if (!runtimeIds.contains(it->first)) { + it = networkNameTags.erase(it); + continue; + } + ++it; + } +} + +void NameTagCache::clearNetworkNameTags() { + networkNameTags.clear(); +} diff --git a/src/client/misc/NameTagCache.h b/src/client/misc/NameTagCache.h new file mode 100644 index 00000000..a268a4bf --- /dev/null +++ b/src/client/misc/NameTagCache.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include +#include +#include + +class NameTagCache { +public: + static void recordNetworkNameTag(uint64_t runtimeId, std::string const& nameTag); + static std::optional getNetworkNameTag(uint64_t runtimeId); + static void retainNetworkNameTags(std::unordered_set const& runtimeIds); + static void clearNetworkNameTags(); +}; diff --git a/src/mc/common/network/Packet.h b/src/mc/common/network/Packet.h index 98a8b4f6..9a6344c0 100644 --- a/src/mc/common/network/Packet.h +++ b/src/mc/common/network/Packet.h @@ -5,6 +5,7 @@ namespace SDK { LOGIN = 0x1, TEXT = 0x9, ACTOR_EVENT = 0x1B, + SET_ENTITY_DATA = 0x27, CHANGE_DIMENSION = 0x3D, TRANSFER = 0x55, SET_TITLE = 0x58, @@ -34,4 +35,4 @@ namespace SDK { virtual bool allowBatch() { return false; }; virtual void _read(void* stream) {}; }; -} \ No newline at end of file +} diff --git a/src/mc/common/network/packet/SetActorDataPacket.cpp b/src/mc/common/network/packet/SetActorDataPacket.cpp new file mode 100644 index 00000000..e2b07e2d --- /dev/null +++ b/src/mc/common/network/packet/SetActorDataPacket.cpp @@ -0,0 +1,37 @@ +#include "pch.h" +#include "SetActorDataPacket.h" + +bool SDK::SetActorDataPacket::tryGetNameTag(uint64_t* runtimeId, std::string* nameTag) const { + if (!runtimeId || !nameTag) return false; + + __try { + constexpr uint16_t nameTagMetadataId = 4; + constexpr uint8_t stringMetadataType = 4; + + auto packetBase = reinterpret_cast(this); + const auto packetRuntimeId = *reinterpret_cast(packetBase + 0x30); + auto* metadataBegin = *reinterpret_cast(packetBase + 0x38); + auto* metadataEnd = *reinterpret_cast(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(metadata); + if (!vtable) continue; + + const auto metadataId = reinterpret_cast(vtable[1])(metadata); + const auto metadataType = reinterpret_cast(vtable[2])(metadata); + if (metadataId != nameTagMetadataId || metadataType != stringMetadataType) continue; + + *runtimeId = packetRuntimeId; + *nameTag = *reinterpret_cast(reinterpret_cast(metadata) + 0x10); + return true; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { + } + + return false; +} diff --git a/src/mc/common/network/packet/SetActorDataPacket.h b/src/mc/common/network/packet/SetActorDataPacket.h new file mode 100644 index 00000000..e715d717 --- /dev/null +++ b/src/mc/common/network/packet/SetActorDataPacket.h @@ -0,0 +1,9 @@ +#pragma once +#include "../Packet.h" + +namespace SDK { + class SetActorDataPacket : public Packet { + public: + bool tryGetNameTag(uint64_t* runtimeId, std::string* nameTag) const; + }; +} From 23f029f04fdc3f838c3e470b488a8c52670b5ed9 Mon Sep 17 00:00:00 2001 From: FuSheng <36793077+Ukiyo-dev@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:56:31 +0800 Subject: [PATCH 2/2] Address review feedback; switch TabList to MCDrawUtil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove onRenderNameTag (substring collision issues per Plextora) - Drop callWhenInactive=true on TickEvent listener - Fix stale coloredNameCache entries in onTick (erase when no nametag or unformatted result, rather than skipping) - Fix drawFormattedText incorrectly resetting color on non-color format codes (§l, §o, etc.) - Remove __try/__except from SetActorDataPacket::tryGetNameTag - Switch rendering from D2DUtil/RenderOverlayEvent to MCDrawUtil/RenderLayerEvent for Minecraft font rendering - Replace D2D transform matrix with Vec2 offset for positioning - Guard render on hud_screen only; add dc.flush() for batched draw - Bump maxPerTab 15 -> 16 --- .../feature/module/modules/hud/TabList.cpp | 92 ++++++------------- .../feature/module/modules/hud/TabList.h | 3 +- .../network/packet/SetActorDataPacket.cpp | 50 +++++----- 3 files changed, 54 insertions(+), 91 deletions(-) diff --git a/src/client/feature/module/modules/hud/TabList.cpp b/src/client/feature/module/modules/hud/TabList.cpp index 61340c28..bfe31346 100644 --- a/src/client/feature/module/modules/hud/TabList.cpp +++ b/src/client/feature/module/modules/hud/TabList.cpp @@ -1,12 +1,14 @@ #include "pch.h" #include "TabList.h" #include "client/Latite.h" -#include "client/event/events/RenderNameTagEvent.h" +#include "client/event/events/RenderLayerEvent.h" #include "client/event/events/TickEvent.h" #include "client/misc/NameTagCache.h" -#include "../../../../render/asset/Assets.h" +#include "mc/common/client/gui/controls/UIControl.h" +#include "mc/common/client/gui/controls/VisualTree.h" #include "mc/common/world/actor/player/Player.h" #include "util/Logger.h" +#include "util/DrawContext.h" #include #include #include @@ -161,7 +163,7 @@ namespace { return formattedName.empty() ? playerName : formattedName; } - void drawFormattedText(D2DUtil& dc, d2d::Rect const& rc, std::string const& text, d2d::Color const& fallbackColor, + void drawFormattedText(DrawUtil& dc, d2d::Rect const& rc, std::string const& text, d2d::Color const& fallbackColor, Renderer::FontSelection font, float textSize) { float x = rc.left; d2d::Color color = fallbackColor; @@ -180,7 +182,9 @@ namespace { size_t codeSize = 0; if (readFormatCode(text, i, code, codeSize)) { flush(); - color = minecraftColor(code, fallbackColor).asAlpha(fallbackColor.a); + if (isColorCode(code) || std::tolower(static_cast(code)) == 'r') { + color = minecraftColor(code, fallbackColor).asAlpha(fallbackColor.a); + } i += codeSize; continue; } @@ -208,28 +212,8 @@ TabList::TabList() : Module("PlayerList", LocalizeString::get("client.module.tab LocalizeString::get("client.module.tabList.bgColor.desc"), bgCol); addSliderSetting("textSize", LocalizeString::get("client.textmodule.props.textSize.name"), L"", textSizeS, FloatValue(2.f), FloatValue(100.f), FloatValue(2.f)); - listen(static_cast(&TabList::onRenderOverlay)); - listen(static_cast(&TabList::onRenderNameTag), true); - listen(static_cast(&TabList::onTick), true); -} - -void TabList::onRenderNameTag(Event& evG) { - auto& ev = static_cast(evG); - auto* tag = ev.getNametag(); - if (!tag || !hasFormatCode(*tag)) return; - - auto* client = SDK::ClientInstance::get(); - if (!client || !client->minecraft) return; - - auto* level = client->minecraft->getLevel(); - if (!level || !level->getPlayerList()) return; - - for (auto& ent : *level->getPlayerList()) { - std::string rowName = colorizedPlayerName(ent.second.name, *tag); - if (hasFormatCode(rowName)) { - coloredNameCache[ent.second.name] = rowName; - } - } + listen(static_cast(&TabList::onRenderLayer)); + listen(static_cast(&TabList::onTick)); } void TabList::onTick(Event& evG) { @@ -257,11 +241,16 @@ void TabList::onTick(Event& evG) { if (!activePlayers.contains(player->playerName)) continue; auto nameTag = NameTagCache::getNetworkNameTag(runtimeId); - if (!nameTag) continue; + if (!nameTag) { + coloredNameCache.erase(player->playerName); + continue; + } std::string rowName = colorizedPlayerName(player->playerName, *nameTag); if (hasFormatCode(rowName)) { coloredNameCache[player->playerName] = rowName; + } else { + coloredNameCache.erase(player->playerName); } } NameTagCache::retainNetworkNameTags(activeRuntimeIds); @@ -290,13 +279,17 @@ void TabList::afterLoadConfig() { } } -void TabList::onRenderOverlay(Event& evG) { - (void)evG; +void TabList::onRenderLayer(Event& evG) { + auto& ev = static_cast(evG); + auto* screenView = ev.getScreenView(); + if (!screenView || !screenView->visualTree || !screenView->visualTree->rootControl + || screenView->visualTree->rootControl->name != "hud_screen") return; auto plr = SDK::ClientInstance::get()->getLocalPlayer(); if (!plr) return; - D2DUtil dc; + MCDrawUtil dc{ ev.getUIRenderContext(), Latite::get().getFont() }; + dc.setImmediate(false); auto lvl = SDK::ClientInstance::get()->minecraft->getLevel(); if (!lvl || !lvl->getPlayerList()) return; @@ -317,26 +310,17 @@ void TabList::onRenderOverlay(Event& evG) { const ColorValue backgroundColor = colorOrDefault(bgCol, ColorValue(0.f, 0.f, 0.f, 0.5f)); float sectionHeight = textP * 1.3f; - float logoSize = sectionHeight; - float logoPad = 4.f; - float longestText = dc.getTextSize(txt, font, textP).x; auto sortedRows = sortedPlayerListRows(lvl, coloredNameCache); for (auto* row : sortedRows) { std::string rowName = tabRowName(*row, coloredNameCache); auto w = dc.getTextSize(util::StrToWStr(stripFormatCodes(rowName)), font, textP).x + 3.f; - for (auto& user : Latite::get().getLatiteUsers()) { - if (user == row->name) { - w += logoPad + logoSize; - } - } - if (w > longestText) longestText = w; } float sectionSize = longestText; - size_t maxPerTab = 15; + size_t maxPerTab = 16; int numTabs = static_cast(std::ceil(static_cast(size) / static_cast(maxPerTab))); float oY = sectionHeight; @@ -346,36 +330,20 @@ void TabList::onRenderOverlay(Event& evG) { size_t idx = 0; - float calcWidth = sectionSize * static_cast(numTabs); float calcHeight = sectionHeight * ((static_cast(size) > maxPerTab) ? maxPerTab : static_cast(size)); - auto& ss = SDK::ClientInstance::get()->getGuiData()->screenSize; - D2D1::Matrix3x2F mat; - dc.ctx->GetTransform(&mat); - dc.ctx->SetTransform(mat * D2D1::Matrix3x2F::Translation(ss.x / 2.f - calcWidth / 2.f, 20.f)); + const Vec2 offset = { ss.x / 2.f - calcWidth / 2.f, 20.f }; + dc.fillRectangle({ offset.x, offset.y, offset.x + calcWidth, offset.y + calcHeight + oY }, backgroundColor.getMainColor()); + dc.drawRectangle({ offset.x, offset.y, offset.x + calcWidth, offset.y + calcHeight + oY }, d2d::Color(backgroundColor.getMainColor()).asAlpha(1.f), 2.f); - dc.fillRectangle({ 0.f, 0.f, calcWidth, calcHeight + oY }, backgroundColor.getMainColor()); - dc.drawRectangle({ 0.f, 0.f, calcWidth, calcHeight + oY }, d2d::Color(backgroundColor.getMainColor()).asAlpha(1.f), 2.f); - - dc.drawText({ 0.f, 0.f, calcWidth, oY }, txt, textColor.getMainColor(), font, textP, DWRITE_TEXT_ALIGNMENT_CENTER); - + dc.drawText({ offset.x, offset.y, offset.x + calcWidth, offset.y + oY }, txt, textColor.getMainColor(), font, textP, DWRITE_TEXT_ALIGNMENT_CENTER); for (auto* row : sortedRows) { std::string rowName = tabRowName(*row, coloredNameCache); - d2d::Rect rc = { x, y, x + longestText, y + sectionHeight }; - for (auto& user : Latite::get().getLatiteUsers()) { - if (user == row->name) { - rc.left += logoSize + logoPad; - d2d::Rect logoRc = { x, y, x + logoSize, y + logoSize }; - dc.ctx->DrawBitmap(Latite::getAssets().logoWhite.getBitmap(), logoRc); - } - } - - // render - //dc.drawRectangle(rc, d2d::Colors::BLACK, 0.5f); + d2d::Rect rc = { offset.x + x, offset.y + y, offset.x + x + longestText, offset.y + y + sectionHeight }; drawFormattedText(dc, rc, rowName, textColor.getMainColor(), font, textP); @@ -388,5 +356,5 @@ void TabList::onRenderOverlay(Event& evG) { y = oY; idx = 0; } - dc.ctx->SetTransform(mat); + dc.flush(); } diff --git a/src/client/feature/module/modules/hud/TabList.h b/src/client/feature/module/modules/hud/TabList.h index d33df891..868ee17a 100644 --- a/src/client/feature/module/modules/hud/TabList.h +++ b/src/client/feature/module/modules/hud/TabList.h @@ -6,8 +6,7 @@ class TabList : public Module { public: TabList(); - void onRenderOverlay(Event&); - void onRenderNameTag(Event&); + void onRenderLayer(Event&); void onTick(Event&); void afterLoadConfig() override; bool shouldHoldToToggle() override { return true; } diff --git a/src/mc/common/network/packet/SetActorDataPacket.cpp b/src/mc/common/network/packet/SetActorDataPacket.cpp index e2b07e2d..73703dcd 100644 --- a/src/mc/common/network/packet/SetActorDataPacket.cpp +++ b/src/mc/common/network/packet/SetActorDataPacket.cpp @@ -4,33 +4,29 @@ bool SDK::SetActorDataPacket::tryGetNameTag(uint64_t* runtimeId, std::string* nameTag) const { if (!runtimeId || !nameTag) return false; - __try { - constexpr uint16_t nameTagMetadataId = 4; - constexpr uint8_t stringMetadataType = 4; - - auto packetBase = reinterpret_cast(this); - const auto packetRuntimeId = *reinterpret_cast(packetBase + 0x30); - auto* metadataBegin = *reinterpret_cast(packetBase + 0x38); - auto* metadataEnd = *reinterpret_cast(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(metadata); - if (!vtable) continue; - - const auto metadataId = reinterpret_cast(vtable[1])(metadata); - const auto metadataType = reinterpret_cast(vtable[2])(metadata); - if (metadataId != nameTagMetadataId || metadataType != stringMetadataType) continue; - - *runtimeId = packetRuntimeId; - *nameTag = *reinterpret_cast(reinterpret_cast(metadata) + 0x10); - return true; - } - } - __except (EXCEPTION_EXECUTE_HANDLER) { + constexpr uint16_t nameTagMetadataId = 4; + constexpr uint8_t stringMetadataType = 4; + + auto packetBase = reinterpret_cast(this); + const auto packetRuntimeId = *reinterpret_cast(packetBase + 0x30); + auto* metadataBegin = *reinterpret_cast(packetBase + 0x38); + auto* metadataEnd = *reinterpret_cast(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(metadata); + if (!vtable) continue; + + const auto metadataId = reinterpret_cast(vtable[1])(metadata); + const auto metadataType = reinterpret_cast(vtable[2])(metadata); + if (metadataId != nameTagMetadataId || metadataType != stringMetadataType) continue; + + *runtimeId = packetRuntimeId; + *nameTag = *reinterpret_cast(reinterpret_cast(metadata) + 0x10); + return true; } return false;