feat: parse SMSG_RESPOND_INSPECT_ACHIEVEMENTS and request on inspect

When the player inspects another player on WotLK 3.3.5a, also send
CMSG_QUERY_INSPECT_ACHIEVEMENTS so the server responds with
SMSG_RESPOND_INSPECT_ACHIEVEMENTS.  The new handler parses the
achievement-id/date sentinel-terminated block (same layout as
SMSG_ALL_ACHIEVEMENT_DATA but prefixed with a packed guid) and stores
the earned achievement IDs keyed by GUID in
inspectedPlayerAchievements_.  The new public getter
getInspectedPlayerAchievements(guid) exposes this data for the inspect
UI.  The cache is cleared on world entry to prevent stale data.
QueryInspectAchievementsPacket::build() handles the CMSG wire format
(uint64 guid + uint8 unk=0).
This commit is contained in:
Kelsi 2026-03-12 23:23:02 -07:00
parent 0089b3a160
commit 1d9dc6dcae
4 changed files with 87 additions and 1 deletions

View file

@ -6884,10 +6884,12 @@ void GameHandler::handlePacket(network::Packet& packet) {
case Opcode::SMSG_REDIRECT_CLIENT:
case Opcode::SMSG_PVP_QUEUE_STATS:
case Opcode::SMSG_NOTIFY_DEST_LOC_SPELL_CAST:
case Opcode::SMSG_RESPOND_INSPECT_ACHIEVEMENTS:
case Opcode::SMSG_PLAYER_SKINNED:
packet.setReadPos(packet.getSize());
break;
case Opcode::SMSG_RESPOND_INSPECT_ACHIEVEMENTS:
handleRespondInspectAchievements(packet);
break;
case Opcode::SMSG_QUEST_POI_QUERY_RESPONSE:
handleQuestPoiQueryResponse(packet);
break;
@ -8039,6 +8041,9 @@ void GameHandler::handleLoginVerifyWorld(network::Packet& packet) {
encounterUnitGuids_.fill(0);
raidTargetGuids_.fill(0);
// Clear inspect caches on world entry to avoid showing stale data
inspectedPlayerAchievements_.clear();
// Reset talent initialization so the first SMSG_TALENTS_INFO after login
// correctly sets the active spec (static locals don't reset across logins)
talentsInitialized_ = false;
@ -11301,6 +11306,12 @@ void GameHandler::inspectTarget() {
auto packet = InspectPacket::build(targetGuid);
socket->send(packet);
// WotLK: also query the player's achievement data so the inspect UI can display it
if (isActiveExpansion("wotlk")) {
auto achPkt = QueryInspectAchievementsPacket::build(targetGuid);
socket->send(achPkt);
}
auto player = std::static_pointer_cast<Player>(target);
std::string name = player->getName().empty() ? "Target" : player->getName();
addSystemChatMessage("Inspecting " + name + "...");
@ -22077,6 +22088,55 @@ void GameHandler::handleAllAchievementData(network::Packet& packet) {
" achievements, ", criteriaProgress_.size(), " criteria");
}
// ---------------------------------------------------------------------------
// SMSG_RESPOND_INSPECT_ACHIEVEMENTS (WotLK 3.3.5a)
// Wire format: packed_guid (inspected player) + same achievement/criteria
// blocks as SMSG_ALL_ACHIEVEMENT_DATA:
// Achievement records: repeated { uint32 id, uint32 packedDate } until 0xFFFFFFFF sentinel
// Criteria records: repeated { uint32 id, uint64 counter, uint32 date, uint32 unk }
// until 0xFFFFFFFF sentinel
// We store only the earned achievement IDs (not criteria) per inspected player.
// ---------------------------------------------------------------------------
void GameHandler::handleRespondInspectAchievements(network::Packet& packet) {
loadAchievementNameCache();
// Read the inspected player's packed guid
if (packet.getSize() - packet.getReadPos() < 1) return;
uint64_t inspectedGuid = UpdateObjectParser::readPackedGuid(packet);
if (inspectedGuid == 0) {
packet.setReadPos(packet.getSize());
return;
}
std::unordered_set<uint32_t> achievements;
// Achievement records: { uint32 id, uint32 packedDate } until sentinel 0xFFFFFFFF
while (packet.getSize() - packet.getReadPos() >= 4) {
uint32_t id = packet.readUInt32();
if (id == 0xFFFFFFFF) break;
if (packet.getSize() - packet.getReadPos() < 4) break;
/*uint32_t date =*/ packet.readUInt32();
achievements.insert(id);
}
// Criteria records: { uint32 id, uint64 counter, uint32 date, uint32 unk }
// until sentinel 0xFFFFFFFF — consume but don't store for inspect use
while (packet.getSize() - packet.getReadPos() >= 4) {
uint32_t id = packet.readUInt32();
if (id == 0xFFFFFFFF) break;
// counter(8) + date(4) + unk(4) = 16 bytes
if (packet.getSize() - packet.getReadPos() < 16) break;
packet.readUInt64(); // counter
packet.readUInt32(); // date
packet.readUInt32(); // unk
}
inspectedPlayerAchievements_[inspectedGuid] = std::move(achievements);
LOG_INFO("SMSG_RESPOND_INSPECT_ACHIEVEMENTS: guid=0x", std::hex, inspectedGuid, std::dec,
" achievements=", inspectedPlayerAchievements_[inspectedGuid].size());
}
// ---------------------------------------------------------------------------
// Faction name cache (lazily loaded from Faction.dbc)
// ---------------------------------------------------------------------------