diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 047220b5..501a0f73 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1490,6 +1490,10 @@ public: using RepChangeCallback = std::function; void setRepChangeCallback(RepChangeCallback cb) { repChangeCallback_ = std::move(cb); } + // PvP honor credit callback (honorable kill or BG reward) + using PvpHonorCallback = std::function; + void setPvpHonorCallback(PvpHonorCallback cb) { pvpHonorCallback_ = std::move(cb); } + // Quest turn-in completion callback using QuestCompleteCallback = std::function; void setQuestCompleteCallback(QuestCompleteCallback cb) { questCompleteCallback_ = std::move(cb); } @@ -2831,6 +2835,9 @@ private: RepChangeCallback repChangeCallback_; uint32_t watchedFactionId_ = 0; // auto-set to most recently changed faction + // ---- PvP honor credit callback ---- + PvpHonorCallback pvpHonorCallback_; + // ---- Quest completion callback ---- QuestCompleteCallback questCompleteCallback_; }; diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index 9fc68363..77fc01d8 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -563,6 +563,17 @@ private: bool otherPlayerLevelUpCallbackSet_ = false; void renderPlayerLevelUpToasts(game::GameHandler& gameHandler); + // PvP honor credit toast ("+N Honor" shown when an honorable kill is credited) + struct PvpHonorToastEntry { + uint32_t honor = 0; + uint32_t victimRank = 0; // 0 = unranked / not available + float age = 0.0f; + }; + static constexpr float PVP_HONOR_TOAST_DURATION = 3.5f; + std::vector pvpHonorToasts_; + bool pvpHonorCallbackSet_ = false; + void renderPvpHonorToasts(); + // 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 8f433ebd..e7911baf 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -1882,6 +1882,9 @@ void GameHandler::handlePacket(network::Packet& packet) { std::dec, " rank=", rank); std::string msg = "You gain " + std::to_string(honor) + " honor points."; addSystemChatMessage(msg); + if (pvpHonorCallback_) { + pvpHonorCallback_(honor, victimGuid, rank); + } } break; } diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 61131666..d7f9d772 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -348,6 +348,17 @@ void GameScreen::render(game::GameHandler& gameHandler) { otherPlayerLevelUpCallbackSet_ = true; } + // Set up PvP honor credit toast callback (once) + if (!pvpHonorCallbackSet_) { + gameHandler.setPvpHonorCallback([this](uint32_t honor, uint64_t /*victimGuid*/, uint32_t rank) { + if (honor == 0) return; + pvpHonorToasts_.push_back({honor, rank, 0.0f}); + if (pvpHonorToasts_.size() > 4) + pvpHonorToasts_.erase(pvpHonorToasts_.begin()); + }); + pvpHonorCallbackSet_ = true; + } + // Set up UI error frame callback (once) if (!uiErrorCallbackSet_) { gameHandler.setUIErrorCallback([this](const std::string& msg) { @@ -680,6 +691,7 @@ void GameScreen::render(game::GameHandler& gameHandler) { renderWhisperToasts(); renderQuestProgressToasts(); renderPlayerLevelUpToasts(gameHandler); + renderPvpHonorToasts(); renderZoneText(); // World map (M key toggle handled inside) @@ -18214,6 +18226,70 @@ void GameScreen::renderQuestProgressToasts() { } } +// --------------------------------------------------------------------------- +// PvP honor credit toasts — shown at screen top-right on honorable kill +// --------------------------------------------------------------------------- + +void GameScreen::renderPvpHonorToasts() { + if (pvpHonorToasts_.empty()) return; + + float dt = ImGui::GetIO().DeltaTime; + for (auto& t : pvpHonorToasts_) t.age += dt; + pvpHonorToasts_.erase( + std::remove_if(pvpHonorToasts_.begin(), pvpHonorToasts_.end(), + [](const PvpHonorToastEntry& t) { return t.age >= PVP_HONOR_TOAST_DURATION; }), + pvpHonorToasts_.end()); + if (pvpHonorToasts_.empty()) return; + + ImVec2 displaySize = ImGui::GetIO().DisplaySize; + float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f; + + // Stack toasts at top-right, below any minimap area + constexpr float TOAST_W = 180.0f; + constexpr float TOAST_H = 30.0f; + constexpr float TOAST_GAP = 3.0f; + constexpr float TOAST_TOP = 10.0f; + float toastX = screenW - TOAST_W - 10.0f; + + ImDrawList* bgDL = ImGui::GetBackgroundDrawList(); + const int count = static_cast(pvpHonorToasts_.size()); + + for (int i = 0; i < count; ++i) { + const auto& toast = pvpHonorToasts_[i]; + + float remaining = PVP_HONOR_TOAST_DURATION - toast.age; + float alpha; + if (toast.age < 0.15f) + alpha = toast.age / 0.15f; + else if (remaining < 0.8f) + alpha = remaining / 0.8f; + else + alpha = 1.0f; + alpha = std::clamp(alpha, 0.0f, 1.0f); + + float ty = TOAST_TOP + i * (TOAST_H + TOAST_GAP); + + uint8_t bgA = static_cast(190 * alpha); + uint8_t fgA = static_cast(255 * alpha); + + // Background: dark red (PvP theme) + bgDL->AddRectFilled(ImVec2(toastX, ty), ImVec2(toastX + TOAST_W, ty + TOAST_H), + IM_COL32(28, 5, 5, bgA), 4.0f); + bgDL->AddRect(ImVec2(toastX, ty), ImVec2(toastX + TOAST_W, ty + TOAST_H), + IM_COL32(200, 50, 50, static_cast(160 * alpha)), 4.0f, 0, 1.2f); + + // Sword ⚔ icon (U+2694, UTF-8: e2 9a 94) + bgDL->AddText(ImVec2(toastX + 7.0f, ty + 7.0f), + IM_COL32(220, 80, 80, fgA), "\xe2\x9a\x94"); + + // "+N Honor" text in gold + char buf[40]; + snprintf(buf, sizeof(buf), "+%u Honor", toast.honor); + bgDL->AddText(ImVec2(toastX + 24.0f, ty + 8.0f), + IM_COL32(255, 210, 50, fgA), buf); + } +} + // --------------------------------------------------------------------------- // Nearby player level-up toasts — shown at screen bottom-centre // ---------------------------------------------------------------------------