feat: show area discovery toast with XP gain when exploring new zones

This commit is contained in:
Kelsi 2026-03-12 15:42:55 -07:00
parent 98ad71df0d
commit 77879769d3
4 changed files with 99 additions and 0 deletions

View file

@ -1428,6 +1428,10 @@ public:
using AchievementEarnedCallback = std::function<void(uint32_t achievementId, const std::string& name)>;
void setAchievementEarnedCallback(AchievementEarnedCallback cb) { achievementEarnedCallback_ = std::move(cb); }
const std::unordered_set<uint32_t>& getEarnedAchievements() const { return earnedAchievements_; }
// Area discovery callback — fires when SMSG_EXPLORATION_EXPERIENCE is received
using AreaDiscoveryCallback = std::function<void(const std::string& areaName, uint32_t xpGained)>;
void setAreaDiscoveryCallback(AreaDiscoveryCallback cb) { areaDiscoveryCallback_ = std::move(cb); }
const std::unordered_map<uint32_t, uint64_t>& getCriteriaProgress() const { return criteriaProgress_; }
/// Returns the WoW PackedTime earn date for an achievement, or 0 if unknown.
uint32_t getAchievementDate(uint32_t id) const {
@ -2749,6 +2753,7 @@ private:
LevelUpCallback levelUpCallback_;
OtherPlayerLevelUpCallback otherPlayerLevelUpCallback_;
AchievementEarnedCallback achievementEarnedCallback_;
AreaDiscoveryCallback areaDiscoveryCallback_;
MountCallback mountCallback_;
TaxiPrecacheCallback taxiPrecacheCallback_;
TaxiOrientationCallback taxiOrientationCallback_;

View file

@ -519,6 +519,14 @@ private:
std::string achievementToastName_;
void renderAchievementToast();
// Area discovery toast ("Discovered! <AreaName> +XP XP")
static constexpr float DISCOVERY_TOAST_DURATION = 4.0f;
float discoveryToastTimer_ = 0.0f;
std::string discoveryToastName_;
uint32_t discoveryToastXP_ = 0;
bool areaDiscoveryCallbackSet_ = false;
void renderDiscoveryToast();
// Zone discovery text ("Entering: <ZoneName>")
static constexpr float ZONE_TEXT_DURATION = 5.0f;
float zoneTextTimer_ = 0.0f;

View file

@ -1767,6 +1767,8 @@ void GameHandler::handlePacket(network::Packet& packet) {
}
addSystemChatMessage(msg);
// XP is updated via PLAYER_XP update fields from the server.
if (areaDiscoveryCallback_)
areaDiscoveryCallback_(areaName, xpGained);
}
}
break;

View file

@ -300,6 +300,16 @@ void GameScreen::render(game::GameHandler& gameHandler) {
achievementCallbackSet_ = true;
}
// Set up area discovery toast callback (once)
if (!areaDiscoveryCallbackSet_) {
gameHandler.setAreaDiscoveryCallback([this](const std::string& areaName, uint32_t xpGained) {
discoveryToastName_ = areaName.empty() ? "New Area" : areaName;
discoveryToastXP_ = xpGained;
discoveryToastTimer_ = DISCOVERY_TOAST_DURATION;
});
areaDiscoveryCallbackSet_ = true;
}
// Set up UI error frame callback (once)
if (!uiErrorCallbackSet_) {
gameHandler.setUIErrorCallback([this](const std::string& msg) {
@ -628,6 +638,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
renderSettingsWindow();
renderDingEffect();
renderAchievementToast();
renderDiscoveryToast();
renderZoneText();
// World map (M key toggle handled inside)
@ -17927,6 +17938,79 @@ void GameScreen::renderAchievementToast() {
IM_COL32(220, 200, 150, (int)(alpha * 255)), idBuf);
}
// ---------------------------------------------------------------------------
// Area discovery toast — "Discovered: <AreaName>! (+XP XP)" centered on screen
// ---------------------------------------------------------------------------
void GameScreen::renderDiscoveryToast() {
if (discoveryToastTimer_ <= 0.0f) return;
float dt = ImGui::GetIO().DeltaTime;
discoveryToastTimer_ -= dt;
if (discoveryToastTimer_ < 0.0f) discoveryToastTimer_ = 0.0f;
// Fade: ramp up in first 0.4s, hold, fade out in last 1.0s
float alpha;
if (discoveryToastTimer_ > DISCOVERY_TOAST_DURATION - 0.4f)
alpha = 1.0f - (discoveryToastTimer_ - (DISCOVERY_TOAST_DURATION - 0.4f)) / 0.4f;
else if (discoveryToastTimer_ < 1.0f)
alpha = discoveryToastTimer_;
else
alpha = 1.0f;
alpha = std::clamp(alpha, 0.0f, 1.0f);
auto* window = core::Application::getInstance().getWindow();
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
ImFont* font = ImGui::GetFont();
ImDrawList* draw = ImGui::GetForegroundDrawList();
const char* header = "Discovered!";
float headerSize = 16.0f;
float nameSize = 28.0f;
float xpSize = 14.0f;
ImVec2 headerDim = font->CalcTextSizeA(headerSize, FLT_MAX, 0.0f, header);
ImVec2 nameDim = font->CalcTextSizeA(nameSize, FLT_MAX, 0.0f, discoveryToastName_.c_str());
char xpBuf[48];
if (discoveryToastXP_ > 0)
snprintf(xpBuf, sizeof(xpBuf), "+%u XP", discoveryToastXP_);
else
xpBuf[0] = '\0';
ImVec2 xpDim = font->CalcTextSizeA(xpSize, FLT_MAX, 0.0f, xpBuf);
// Position slightly below zone text (at 37% down screen)
float centreY = screenH * 0.37f;
float headerX = (screenW - headerDim.x) * 0.5f;
float nameX = (screenW - nameDim.x) * 0.5f;
float xpX = (screenW - xpDim.x) * 0.5f;
float headerY = centreY;
float nameY = centreY + headerDim.y + 4.0f;
float xpY = nameY + nameDim.y + 4.0f;
// "Discovered!" in gold
draw->AddText(font, headerSize, ImVec2(headerX + 1, headerY + 1),
IM_COL32(0, 0, 0, (int)(alpha * 160)), header);
draw->AddText(font, headerSize, ImVec2(headerX, headerY),
IM_COL32(255, 215, 0, (int)(alpha * 255)), header);
// Area name in white
draw->AddText(font, nameSize, ImVec2(nameX + 1, nameY + 1),
IM_COL32(0, 0, 0, (int)(alpha * 160)), discoveryToastName_.c_str());
draw->AddText(font, nameSize, ImVec2(nameX, nameY),
IM_COL32(255, 255, 255, (int)(alpha * 255)), discoveryToastName_.c_str());
// XP gain in light green (if any)
if (xpBuf[0] != '\0') {
draw->AddText(font, xpSize, ImVec2(xpX + 1, xpY + 1),
IM_COL32(0, 0, 0, (int)(alpha * 140)), xpBuf);
draw->AddText(font, xpSize, ImVec2(xpX, xpY),
IM_COL32(100, 220, 100, (int)(alpha * 230)), xpBuf);
}
}
// ---------------------------------------------------------------------------
// Zone discovery text — "Entering: <ZoneName>" fades in/out at screen centre
// ---------------------------------------------------------------------------