mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
feat: add duel countdown overlay (3-2-1-Fight!)
Parse SMSG_DUEL_COUNTDOWN to get the countdown duration, track the start time, and render a large centered countdown overlay. Numbers display in pulsing gold; transitions to pulsing red 'Fight!' for the last 0.5 seconds. Countdown clears on SMSG_DUEL_COMPLETE.
This commit is contained in:
parent
29a989e1f4
commit
c4979cd8e2
4 changed files with 63 additions and 2 deletions
|
|
@ -1035,6 +1035,14 @@ public:
|
||||||
const std::string& getDuelChallengerName() const { return duelChallengerName_; }
|
const std::string& getDuelChallengerName() const { return duelChallengerName_; }
|
||||||
void acceptDuel();
|
void acceptDuel();
|
||||||
// forfeitDuel() already declared at line ~399
|
// forfeitDuel() already declared at line ~399
|
||||||
|
// Returns remaining duel countdown seconds, or 0 if no active countdown
|
||||||
|
float getDuelCountdownRemaining() const {
|
||||||
|
if (duelCountdownMs_ == 0) return 0.0f;
|
||||||
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now() - duelCountdownStartedAt_).count();
|
||||||
|
float rem = (static_cast<float>(duelCountdownMs_) - static_cast<float>(elapsed)) / 1000.0f;
|
||||||
|
return rem > 0.0f ? rem : 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Instance lockouts ----
|
// ---- Instance lockouts ----
|
||||||
struct InstanceLockout {
|
struct InstanceLockout {
|
||||||
|
|
@ -2251,6 +2259,8 @@ private:
|
||||||
uint64_t duelChallengerGuid_= 0;
|
uint64_t duelChallengerGuid_= 0;
|
||||||
uint64_t duelFlagGuid_ = 0;
|
uint64_t duelFlagGuid_ = 0;
|
||||||
std::string duelChallengerName_;
|
std::string duelChallengerName_;
|
||||||
|
uint32_t duelCountdownMs_ = 0; // 0 = no active countdown
|
||||||
|
std::chrono::steady_clock::time_point duelCountdownStartedAt_{};
|
||||||
|
|
||||||
// ---- Guild state ----
|
// ---- Guild state ----
|
||||||
std::string guildName_;
|
std::string guildName_;
|
||||||
|
|
|
||||||
|
|
@ -293,6 +293,7 @@ private:
|
||||||
void renderQuestCompleteToasts(float deltaTime);
|
void renderQuestCompleteToasts(float deltaTime);
|
||||||
void renderGroupInvitePopup(game::GameHandler& gameHandler);
|
void renderGroupInvitePopup(game::GameHandler& gameHandler);
|
||||||
void renderDuelRequestPopup(game::GameHandler& gameHandler);
|
void renderDuelRequestPopup(game::GameHandler& gameHandler);
|
||||||
|
void renderDuelCountdown(game::GameHandler& gameHandler);
|
||||||
void renderLootRollPopup(game::GameHandler& gameHandler);
|
void renderLootRollPopup(game::GameHandler& gameHandler);
|
||||||
void renderTradeRequestPopup(game::GameHandler& gameHandler);
|
void renderTradeRequestPopup(game::GameHandler& gameHandler);
|
||||||
void renderTradeWindow(game::GameHandler& gameHandler);
|
void renderTradeWindow(game::GameHandler& gameHandler);
|
||||||
|
|
|
||||||
|
|
@ -3214,9 +3214,16 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
case Opcode::SMSG_DUEL_INBOUNDS:
|
case Opcode::SMSG_DUEL_INBOUNDS:
|
||||||
// Re-entered the duel area; no special action needed.
|
// Re-entered the duel area; no special action needed.
|
||||||
break;
|
break;
|
||||||
case Opcode::SMSG_DUEL_COUNTDOWN:
|
case Opcode::SMSG_DUEL_COUNTDOWN: {
|
||||||
// Countdown timer — no action needed; server also sends UNIT_FIELD_FLAGS update.
|
// uint32 countdown in milliseconds (typically 3000 ms)
|
||||||
|
if (packet.getSize() - packet.getReadPos() >= 4) {
|
||||||
|
uint32_t ms = packet.readUInt32();
|
||||||
|
duelCountdownMs_ = (ms > 0 && ms <= 30000) ? ms : 3000;
|
||||||
|
duelCountdownStartedAt_ = std::chrono::steady_clock::now();
|
||||||
|
LOG_INFO("SMSG_DUEL_COUNTDOWN: ", duelCountdownMs_, " ms");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case Opcode::SMSG_PARTYKILLLOG: {
|
case Opcode::SMSG_PARTYKILLLOG: {
|
||||||
// uint64 killerGuid + uint64 victimGuid
|
// uint64 killerGuid + uint64 victimGuid
|
||||||
if (packet.getSize() - packet.getReadPos() < 16) break;
|
if (packet.getSize() - packet.getReadPos() < 16) break;
|
||||||
|
|
@ -10472,6 +10479,7 @@ void GameHandler::handleDuelComplete(network::Packet& packet) {
|
||||||
uint8_t started = packet.readUInt8();
|
uint8_t started = packet.readUInt8();
|
||||||
// started=1: duel began, started=0: duel was cancelled before starting
|
// started=1: duel began, started=0: duel was cancelled before starting
|
||||||
pendingDuelRequest_ = false;
|
pendingDuelRequest_ = false;
|
||||||
|
duelCountdownMs_ = 0; // clear countdown once duel is resolved
|
||||||
if (!started) {
|
if (!started) {
|
||||||
addSystemChatMessage("The duel was cancelled.");
|
addSystemChatMessage("The duel was cancelled.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -482,6 +482,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
||||||
renderBossFrames(gameHandler);
|
renderBossFrames(gameHandler);
|
||||||
renderGroupInvitePopup(gameHandler);
|
renderGroupInvitePopup(gameHandler);
|
||||||
renderDuelRequestPopup(gameHandler);
|
renderDuelRequestPopup(gameHandler);
|
||||||
|
renderDuelCountdown(gameHandler);
|
||||||
renderLootRollPopup(gameHandler);
|
renderLootRollPopup(gameHandler);
|
||||||
renderTradeRequestPopup(gameHandler);
|
renderTradeRequestPopup(gameHandler);
|
||||||
renderTradeWindow(gameHandler);
|
renderTradeWindow(gameHandler);
|
||||||
|
|
@ -7488,6 +7489,47 @@ void GameScreen::renderDuelRequestPopup(game::GameHandler& gameHandler) {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameScreen::renderDuelCountdown(game::GameHandler& gameHandler) {
|
||||||
|
float remaining = gameHandler.getDuelCountdownRemaining();
|
||||||
|
if (remaining <= 0.0f) return;
|
||||||
|
|
||||||
|
ImVec2 displaySize = ImGui::GetIO().DisplaySize;
|
||||||
|
float screenW = displaySize.x > 0.0f ? displaySize.x : 1280.0f;
|
||||||
|
float screenH = displaySize.y > 0.0f ? displaySize.y : 720.0f;
|
||||||
|
|
||||||
|
auto* dl = ImGui::GetForegroundDrawList();
|
||||||
|
ImFont* font = ImGui::GetFont();
|
||||||
|
float fontSize = ImGui::GetFontSize();
|
||||||
|
|
||||||
|
// Show integer countdown or "Fight!" when under 0.5s
|
||||||
|
char buf[32];
|
||||||
|
if (remaining > 0.5f) {
|
||||||
|
snprintf(buf, sizeof(buf), "%d", static_cast<int>(std::ceil(remaining)));
|
||||||
|
} else {
|
||||||
|
snprintf(buf, sizeof(buf), "Fight!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Large font by scaling — use 4x font size for dramatic effect
|
||||||
|
float scale = 4.0f;
|
||||||
|
float scaledSize = fontSize * scale;
|
||||||
|
ImVec2 textSz = font->CalcTextSizeA(scaledSize, FLT_MAX, 0.0f, buf);
|
||||||
|
float tx = (screenW - textSz.x) * 0.5f;
|
||||||
|
float ty = screenH * 0.35f - textSz.y * 0.5f;
|
||||||
|
|
||||||
|
// Pulsing alpha: fades in and out per second
|
||||||
|
float pulse = 0.75f + 0.25f * std::sin(static_cast<float>(ImGui::GetTime()) * 6.28f);
|
||||||
|
uint8_t alpha = static_cast<uint8_t>(255 * pulse);
|
||||||
|
|
||||||
|
// Color: golden countdown, red "Fight!"
|
||||||
|
ImU32 color = (remaining > 0.5f)
|
||||||
|
? IM_COL32(255, 200, 50, alpha)
|
||||||
|
: IM_COL32(255, 60, 60, alpha);
|
||||||
|
|
||||||
|
// Drop shadow
|
||||||
|
dl->AddText(font, scaledSize, ImVec2(tx + 2.0f, ty + 2.0f), IM_COL32(0, 0, 0, alpha / 2), buf);
|
||||||
|
dl->AddText(font, scaledSize, ImVec2(tx, ty), color, buf);
|
||||||
|
}
|
||||||
|
|
||||||
void GameScreen::renderItemTextWindow(game::GameHandler& gameHandler) {
|
void GameScreen::renderItemTextWindow(game::GameHandler& gameHandler) {
|
||||||
if (!gameHandler.isItemTextOpen()) return;
|
if (!gameHandler.isItemTextOpen()) return;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue