Handle SMSG_ACHIEVEMENT_EARNED with toast banner and chat notification

- Parse SMSG_ACHIEVEMENT_EARNED (guid + achievementId + PackedTime date)
  and fire AchievementEarnedCallback for self, chat notify for others
- Add renderAchievementToast() to GameScreen: slides in from right,
  gold-bordered panel with "Achievement Earned!" title + ID, 5s duration
  with 0.4s slide-in/out animation and fade at end
- Add triggerAchievementToast(uint32_t) public method on GameScreen
- Wire AchievementEarnedCallback in application.cpp
- Add playAchievementAlert() to UiSoundManager, loads
  Sound\Interface\AchievementSound.wav with level-up fallback
- SMSG_ALL_ACHIEVEMENT_DATA silently consumed (no tracker UI yet)
This commit is contained in:
Kelsi 2026-03-09 13:53:42 -07:00
parent 200a00d4f5
commit e4f53ce0c3
7 changed files with 159 additions and 0 deletions

View file

@ -1825,6 +1825,12 @@ void GameHandler::handlePacket(network::Packet& packet) {
}
break;
}
case Opcode::SMSG_ACHIEVEMENT_EARNED:
handleAchievementEarned(packet);
break;
case Opcode::SMSG_ALL_ACHIEVEMENT_DATA:
// Initial data burst on login — ignored for now (no achievement tracker UI).
break;
case Opcode::SMSG_CANCEL_AUTO_REPEAT:
break; // Server signals to stop a repeating spell (wand/shoot); no client action needed
case Opcode::SMSG_AURA_UPDATE:
@ -14870,5 +14876,54 @@ void GameHandler::handleAuctionCommandResult(network::Packet& packet) {
" error=", result.errorCode);
}
// ---------------------------------------------------------------------------
// SMSG_ACHIEVEMENT_EARNED (WotLK 3.3.5a wire 0x4AB)
// uint64 guid — player who earned it (may be another player)
// uint32 achievementId — Achievement.dbc ID
// PackedTime date — uint32 bitfield (seconds since epoch)
// uint32 realmFirst — how many on realm also got it (0 = realm first)
// ---------------------------------------------------------------------------
void GameHandler::handleAchievementEarned(network::Packet& packet) {
size_t remaining = packet.getSize() - packet.getReadPos();
if (remaining < 16) return; // guid(8) + id(4) + date(4)
uint64_t guid = packet.readUInt64();
uint32_t achievementId = packet.readUInt32();
/*uint32_t date =*/ packet.readUInt32(); // PackedTime — not displayed
// Show chat notification
bool isSelf = (guid == playerGuid);
if (isSelf) {
char buf[128];
std::snprintf(buf, sizeof(buf),
"Achievement earned! (ID %u)", achievementId);
addSystemChatMessage(buf);
if (achievementEarnedCallback_) {
achievementEarnedCallback_(achievementId);
}
} else {
// Another player in the zone earned an achievement
std::string senderName;
auto entity = entityManager.getEntity(guid);
if (auto* unit = dynamic_cast<Unit*>(entity.get())) {
senderName = unit->getName();
}
if (senderName.empty()) {
char tmp[32];
std::snprintf(tmp, sizeof(tmp), "0x%llX",
static_cast<unsigned long long>(guid));
senderName = tmp;
}
char buf[256];
std::snprintf(buf, sizeof(buf),
"%s has earned an achievement! (ID %u)", senderName.c_str(), achievementId);
addSystemChatMessage(buf);
}
LOG_INFO("SMSG_ACHIEVEMENT_EARNED: guid=0x", std::hex, guid, std::dec,
" achievementId=", achievementId, " self=", isSelf);
}
} // namespace game
} // namespace wowee