feat: battleground invitation popup with countdown timer

Replace the text-only "/join to enter" message with an interactive
popup that shows the BG name, a live countdown progress bar, and
Enter/Leave Queue buttons.

- Parse STATUS_WAIT_JOIN timeout from SMSG_BATTLEFIELD_STATUS
- Store inviteReceivedTime (steady_clock) on the queue slot
- BgQueueSlot moved to public section so UI can read invite details
- Add declineBattlefield() that sends CMSG_BATTLEFIELD_PORT(action=0)
- acceptBattlefield() optimistically sets statusId=3 to dismiss popup
- renderBgInvitePopup: colored countdown bar (green→yellow→red),
  named BG (Alterac Valley, Warsong Gulch, etc.), auto-dismisses on expiry
This commit is contained in:
Kelsi 2026-03-10 21:12:28 -07:00
parent 4986308581
commit a7a559cdcc
4 changed files with 184 additions and 8 deletions

View file

@ -11832,12 +11832,41 @@ void GameHandler::handleBattlefieldStatus(network::Packet& packet) {
bgName = std::to_string(arenaType) + "v" + std::to_string(arenaType) + " Arena";
}
// Parse status-specific fields
uint32_t inviteTimeout = 80; // default WoW BG invite window (seconds)
if (statusId == 1) {
// STATUS_WAIT_QUEUE: avgWaitTime(4) + timeInQueue(4)
if (packet.getSize() - packet.getReadPos() >= 8) {
/*uint32_t avgWait =*/ packet.readUInt32();
/*uint32_t inQueue =*/ packet.readUInt32();
}
} else if (statusId == 2) {
// STATUS_WAIT_JOIN: timeout(4) + mapId(4)
if (packet.getSize() - packet.getReadPos() >= 4) {
inviteTimeout = packet.readUInt32();
}
if (packet.getSize() - packet.getReadPos() >= 4) {
/*uint32_t mapId =*/ packet.readUInt32();
}
} else if (statusId == 3) {
// STATUS_IN_PROGRESS: mapId(4) + timeSinceStart(4)
if (packet.getSize() - packet.getReadPos() >= 8) {
/*uint32_t mapId =*/ packet.readUInt32();
/*uint32_t elapsed =*/ packet.readUInt32();
}
}
// Store queue state
if (queueSlot < bgQueues_.size()) {
bool wasInvite = (bgQueues_[queueSlot].statusId == 2);
bgQueues_[queueSlot].queueSlot = queueSlot;
bgQueues_[queueSlot].bgTypeId = bgTypeId;
bgQueues_[queueSlot].arenaType = arenaType;
bgQueues_[queueSlot].statusId = statusId;
if (statusId == 2 && !wasInvite) {
bgQueues_[queueSlot].inviteTimeout = inviteTimeout;
bgQueues_[queueSlot].inviteReceivedTime = std::chrono::steady_clock::now();
}
}
switch (statusId) {
@ -11849,8 +11878,10 @@ void GameHandler::handleBattlefieldStatus(network::Packet& packet) {
LOG_INFO("Battlefield status: WAIT_QUEUE for ", bgName);
break;
case 2: // STATUS_WAIT_JOIN
addSystemChatMessage(bgName + " is ready! Type /join to enter.");
LOG_INFO("Battlefield status: WAIT_JOIN for ", bgName);
// Popup shown by the UI; add chat notification too.
addSystemChatMessage(bgName + " is ready!");
LOG_INFO("Battlefield status: WAIT_JOIN for ", bgName,
" timeout=", inviteTimeout, "s");
break;
case 3: // STATUS_IN_PROGRESS
addSystemChatMessage("Entered " + bgName + ".");
@ -11865,6 +11896,44 @@ void GameHandler::handleBattlefieldStatus(network::Packet& packet) {
}
}
void GameHandler::declineBattlefield(uint32_t queueSlot) {
if (state != WorldState::IN_WORLD) return;
if (!socket) return;
const BgQueueSlot* slot = nullptr;
if (queueSlot == 0xFFFFFFFF) {
for (const auto& s : bgQueues_) {
if (s.statusId == 2) { slot = &s; break; }
}
} else if (queueSlot < bgQueues_.size() && bgQueues_[queueSlot].statusId == 2) {
slot = &bgQueues_[queueSlot];
}
if (!slot) {
addSystemChatMessage("No battleground invitation pending.");
return;
}
// CMSG_BATTLEFIELD_PORT with action=0 (decline)
network::Packet pkt(wireOpcode(Opcode::CMSG_BATTLEFIELD_PORT));
pkt.writeUInt8(slot->arenaType);
pkt.writeUInt8(0x00);
pkt.writeUInt32(slot->bgTypeId);
pkt.writeUInt16(0x0000);
pkt.writeUInt8(0); // 0 = decline
socket->send(pkt);
// Clear queue slot
uint32_t clearSlot = slot->queueSlot;
if (clearSlot < bgQueues_.size()) {
bgQueues_[clearSlot] = BgQueueSlot{};
}
addSystemChatMessage("Battleground invitation declined.");
LOG_INFO("Sent CMSG_BATTLEFIELD_PORT: decline");
}
bool GameHandler::hasPendingBgInvite() const {
for (const auto& slot : bgQueues_) {
if (slot.statusId == 2) return true; // STATUS_WAIT_JOIN
@ -11901,6 +11970,12 @@ void GameHandler::acceptBattlefield(uint32_t queueSlot) {
socket->send(pkt);
// Optimistically clear the invite so the popup disappears immediately.
uint32_t clearSlot = slot->queueSlot;
if (clearSlot < bgQueues_.size()) {
bgQueues_[clearSlot].statusId = 3; // STATUS_IN_PROGRESS (server will confirm)
}
addSystemChatMessage("Accepting battleground invitation...");
LOG_INFO("Sent CMSG_BATTLEFIELD_PORT: accept bgTypeId=", slot->bgTypeId);
}