diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 81b02737..a02077f1 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1428,6 +1428,10 @@ public: using AchievementEarnedCallback = std::function; void setAchievementEarnedCallback(AchievementEarnedCallback cb) { achievementEarnedCallback_ = std::move(cb); } const std::unordered_set& getEarnedAchievements() const { return earnedAchievements_; } + + // Area discovery callback — fires when SMSG_EXPLORATION_EXPERIENCE is received + using AreaDiscoveryCallback = std::function; + void setAreaDiscoveryCallback(AreaDiscoveryCallback cb) { areaDiscoveryCallback_ = std::move(cb); } const std::unordered_map& 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_; diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index 5f75350c..f557ec0e 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -519,6 +519,14 @@ private: std::string achievementToastName_; void renderAchievementToast(); + // Area discovery toast ("Discovered! +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: ") static constexpr float ZONE_TEXT_DURATION = 5.0f; float zoneTextTimer_ = 0.0f; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 3439ffc1..0ff47db3 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -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; diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 5b7332f2..aa4cef65 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -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: ! (+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(window->getWidth()) : 1280.0f; + float screenH = window ? static_cast(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: " fades in/out at screen centre // ---------------------------------------------------------------------------