diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index e75fedb5..d917890a 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1183,6 +1183,8 @@ public: void cancelPetUnlearn() { petUnlearnPending_ = false; } /** True when ghost is within 40 yards of corpse position (same map). */ bool canReclaimCorpse() const; + /** Seconds remaining on the PvP corpse-reclaim delay, or 0 if the reclaim is available now. */ + float getCorpseReclaimDelaySec() const; /** Distance (yards) from ghost to corpse, or -1 if no corpse data. */ float getCorpseDistance() const { if (corpseMapId_ == 0 || currentMapId_ != corpseMapId_) return -1.0f; @@ -3298,6 +3300,9 @@ private: uint32_t corpseMapId_ = 0; float corpseX_ = 0.0f, corpseY_ = 0.0f, corpseZ_ = 0.0f; uint64_t corpseGuid_ = 0; + // Absolute time (ms since epoch) when PvP corpse-reclaim delay expires. + // 0 means no active delay (reclaim allowed immediately upon proximity). + uint64_t corpseReclaimAvailableMs_ = 0; // Death Knight runes (class 6): slots 0-1=Blood, 2-3=Unholy, 4-5=Frost initially std::array playerRunes_ = [] { std::array r{}; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 2fec9a1d..58b0b622 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -2645,13 +2645,14 @@ void GameHandler::handlePacket(network::Packet& packet) { break; } case Opcode::SMSG_CORPSE_RECLAIM_DELAY: { - // uint32 delayMs before player can reclaim corpse + // uint32 delayMs before player can reclaim corpse (PvP deaths) if (packet.getSize() - packet.getReadPos() >= 4) { uint32_t delayMs = packet.readUInt32(); - uint32_t delaySec = (delayMs + 999) / 1000; - addSystemChatMessage("You can reclaim your corpse in " + - std::to_string(delaySec) + " seconds."); - LOG_DEBUG("SMSG_CORPSE_RECLAIM_DELAY: ", delayMs, "ms"); + auto nowMs = static_cast( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count()); + corpseReclaimAvailableMs_ = nowMs + delayMs; + LOG_INFO("SMSG_CORPSE_RECLAIM_DELAY: ", delayMs, "ms"); } break; } @@ -9085,6 +9086,7 @@ void GameHandler::selectCharacter(uint64_t characterGuid) { playerDead_ = false; releasedSpirit_ = false; corpseGuid_ = 0; + corpseReclaimAvailableMs_ = 0; targetGuid = 0; focusGuid = 0; lastTargetGuid = 0; @@ -13941,6 +13943,15 @@ bool GameHandler::canReclaimCorpse() const { return (dx*dx + dy*dy + dz*dz) <= (40.0f * 40.0f); } +float GameHandler::getCorpseReclaimDelaySec() const { + if (corpseReclaimAvailableMs_ == 0) return 0.0f; + auto nowMs = static_cast( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count()); + if (nowMs >= corpseReclaimAvailableMs_) return 0.0f; + return static_cast(corpseReclaimAvailableMs_ - nowMs) / 1000.0f; +} + void GameHandler::reclaimCorpse() { if (!canReclaimCorpse() || !socket) return; // CMSG_RECLAIM_CORPSE requires the corpse object's own GUID. diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 8cd7a6c8..808a922b 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -15421,28 +15421,48 @@ void GameScreen::renderReclaimCorpseButton(game::GameHandler& gameHandler) { float screenW = window ? static_cast(window->getWidth()) : 1280.0f; float screenH = window ? static_cast(window->getHeight()) : 720.0f; + float delaySec = gameHandler.getCorpseReclaimDelaySec(); + bool onDelay = (delaySec > 0.0f); + float btnW = 220.0f, btnH = 36.0f; + float winH = btnH + 16.0f + (onDelay ? 20.0f : 0.0f); ImGui::SetNextWindowPos(ImVec2(screenW / 2 - btnW / 2, screenH * 0.72f), ImGuiCond_Always); - ImGui::SetNextWindowSize(ImVec2(btnW + 16.0f, btnH + 16.0f), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(btnW + 16.0f, winH), ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 8.0f)); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.7f)); if (ImGui::Begin("##ReclaimCorpse", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus)) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.35f, 0.15f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.55f, 0.25f, 1.0f)); - if (ImGui::Button("Resurrect from Corpse", ImVec2(btnW, btnH))) { - gameHandler.reclaimCorpse(); - } - ImGui::PopStyleColor(2); - float corpDist = gameHandler.getCorpseDistance(); - if (corpDist >= 0.0f) { - char distBuf[48]; - snprintf(distBuf, sizeof(distBuf), "Corpse: %.0f yards away", corpDist); - float dw = ImGui::CalcTextSize(distBuf).x; - ImGui::SetCursorPosX((btnW + 16.0f - dw) * 0.5f); - ImGui::TextDisabled("%s", distBuf); + if (onDelay) { + // Greyed-out button while PvP reclaim timer ticks down + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.25f, 0.25f, 0.25f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.25f, 0.25f, 1.0f)); + ImGui::BeginDisabled(true); + char delayLabel[64]; + snprintf(delayLabel, sizeof(delayLabel), "Resurrect from Corpse (%.0fs)", delaySec); + ImGui::Button(delayLabel, ImVec2(btnW, btnH)); + ImGui::EndDisabled(); + ImGui::PopStyleColor(2); + const char* waitMsg = "You cannot reclaim your corpse yet."; + float tw = ImGui::CalcTextSize(waitMsg).x; + ImGui::SetCursorPosX((btnW + 16.0f - tw) * 0.5f); + ImGui::TextColored(ImVec4(0.8f, 0.5f, 0.2f, 1.0f), "%s", waitMsg); + } else { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.35f, 0.15f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.55f, 0.25f, 1.0f)); + if (ImGui::Button("Resurrect from Corpse", ImVec2(btnW, btnH))) { + gameHandler.reclaimCorpse(); + } + ImGui::PopStyleColor(2); + float corpDist = gameHandler.getCorpseDistance(); + if (corpDist >= 0.0f) { + char distBuf[48]; + snprintf(distBuf, sizeof(distBuf), "Corpse: %.0f yards away", corpDist); + float dw = ImGui::CalcTextSize(distBuf).x; + ImGui::SetCursorPosX((btnW + 16.0f - dw) * 0.5f); + ImGui::TextDisabled("%s", distBuf); + } } } ImGui::End();