diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 47997040..02bd74cb 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -608,6 +608,13 @@ public: uint32_t getPetitionCost() const { return petitionCost_; } uint64_t getPetitionNpcGuid() const { return petitionNpcGuid_; } + // Guild name lookup for other players' nameplates + // Returns the guild name for a given guildId, or empty if unknown. + // Automatically queries the server for unknown guild IDs. + const std::string& lookupGuildName(uint32_t guildId); + // Returns the guildId for a player entity (from PLAYER_GUILDID update field). + uint32_t getEntityGuildId(uint64_t guid) const; + // Ready check struct ReadyCheckResult { std::string name; @@ -2952,6 +2959,8 @@ private: GuildInfoData guildInfoData_; GuildQueryResponseData guildQueryData_; bool hasGuildRoster_ = false; + std::unordered_map guildNameCache_; // guildId → guild name + std::unordered_set pendingGuildNameQueries_; // in-flight guild queries bool pendingGuildInvite_ = false; std::string pendingGuildInviterName_; std::string pendingGuildInviteGuildName_; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 0cdfe948..15e55819 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -759,6 +759,8 @@ void GameHandler::disconnect() { activeCharacterGuid_ = 0; playerNameCache.clear(); pendingNameQueries.clear(); + guildNameCache_.clear(); + pendingGuildNameQueries_.clear(); friendGuids_.clear(); contacts_.clear(); transportAttachments_.clear(); @@ -19577,6 +19579,28 @@ void GameHandler::queryGuildInfo(uint32_t guildId) { LOG_INFO("Querying guild info: guildId=", guildId); } +static const std::string kEmptyString; + +const std::string& GameHandler::lookupGuildName(uint32_t guildId) { + if (guildId == 0) return kEmptyString; + auto it = guildNameCache_.find(guildId); + if (it != guildNameCache_.end()) return it->second; + // Query the server if we haven't already + if (pendingGuildNameQueries_.insert(guildId).second) { + queryGuildInfo(guildId); + } + return kEmptyString; +} + +uint32_t GameHandler::getEntityGuildId(uint64_t guid) const { + auto entity = entityManager.getEntity(guid); + if (!entity || entity->getType() != ObjectType::PLAYER) return 0; + // PLAYER_GUILDID = UNIT_END + 3 across all expansions + const uint16_t ufUnitEnd = fieldIndex(UF::UNIT_END); + if (ufUnitEnd == 0xFFFF) return 0; + return entity->getField(ufUnitEnd + 3); +} + void GameHandler::createGuild(const std::string& guildName) { if (state != WorldState::IN_WORLD || !socket) return; auto packet = GuildCreatePacket::build(guildName); @@ -19661,18 +19685,30 @@ void GameHandler::handleGuildQueryResponse(network::Packet& packet) { GuildQueryResponseData data; if (!packetParsers_->parseGuildQueryResponse(packet, data)) return; - const bool wasUnknown = guildName_.empty(); - guildName_ = data.guildName; - guildQueryData_ = data; - guildRankNames_.clear(); - for (uint32_t i = 0; i < 10; ++i) { - guildRankNames_.push_back(data.rankNames[i]); + // Always cache the guild name for nameplate lookups + if (data.guildId != 0 && !data.guildName.empty()) { + guildNameCache_[data.guildId] = data.guildName; + pendingGuildNameQueries_.erase(data.guildId); + } + + // Check if this is the local player's guild + const Character* ch = getActiveCharacter(); + bool isLocalGuild = (ch && ch->hasGuild() && ch->guildId == data.guildId); + + if (isLocalGuild) { + const bool wasUnknown = guildName_.empty(); + guildName_ = data.guildName; + guildQueryData_ = data; + guildRankNames_.clear(); + for (uint32_t i = 0; i < 10; ++i) { + guildRankNames_.push_back(data.rankNames[i]); + } + LOG_INFO("Guild name set to: ", guildName_); + if (wasUnknown && !guildName_.empty()) + addSystemChatMessage("Guild: <" + guildName_ + ">"); + } else { + LOG_INFO("Cached guild name: id=", data.guildId, " name=", data.guildName); } - LOG_INFO("Guild name set to: ", guildName_); - // Only announce once — when we first learn our own guild name at login. - // Subsequent queries (e.g. querying other players' guilds) are silent. - if (wasUnknown && !guildName_.empty()) - addSystemChatMessage("Guild: <" + guildName_ + ">"); } void GameHandler::handleGuildEvent(network::Packet& packet) { diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 88130e6d..902940e8 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -11125,9 +11125,29 @@ void GameScreen::renderNameplates(game::GameHandler& gameHandler) { ? IM_COL32(220, 80, 80, A(230)) // red — hostile NPC : IM_COL32(240, 200, 100, A(230)); // yellow — friendly NPC } + // Guild name for player nameplates: shift name up to make room + std::string guildTag; + if (isPlayer) { + uint32_t guildId = gameHandler.getEntityGuildId(guid); + if (guildId != 0) { + const std::string& gn = gameHandler.lookupGuildName(guildId); + if (!gn.empty()) guildTag = "<" + gn + ">"; + } + } + if (!guildTag.empty()) nameY -= 10.0f; // shift name up for guild line + drawList->AddText(ImVec2(nameX + 1.0f, nameY + 1.0f), IM_COL32(0, 0, 0, A(160)), labelBuf); drawList->AddText(ImVec2(nameX, nameY), nameColor, labelBuf); + // Guild tag below the name (WoW-style in lighter color) + if (!guildTag.empty()) { + ImVec2 guildSz = ImGui::CalcTextSize(guildTag.c_str()); + float guildX = sx - guildSz.x * 0.5f; + float guildY = nameY + textSize.y + 1.0f; + drawList->AddText(ImVec2(guildX + 1.0f, guildY + 1.0f), IM_COL32(0, 0, 0, A(120)), guildTag.c_str()); + drawList->AddText(ImVec2(guildX, guildY), IM_COL32(180, 180, 180, A(200)), guildTag.c_str()); + } + // Group leader crown to the right of the name on player nameplates if (isPlayer && gameHandler.isInGroup() && gameHandler.getPartyData().leaderGuid == guid) {