Handle SMSG_SUMMON_REQUEST with accept/decline popup

- Parse SMSG_SUMMON_REQUEST (summonerGuid + zoneId + timeoutMs),
  store summoner name from entity list, show chat notification
- acceptSummon() sends CMSG_SUMMON_RESPONSE(1), declineSummon() sends
  CMSG_SUMMON_RESPONSE(0), SMSG_SUMMON_CANCEL clears pending state
- renderSummonRequestPopup(): shows summoner name + countdown timer
  with Accept/Decline buttons
This commit is contained in:
Kelsi 2026-03-09 14:07:50 -07:00
parent f369fe9c6e
commit 770ac645d5
4 changed files with 101 additions and 0 deletions

View file

@ -707,6 +707,13 @@ public:
bool hasPendingGroupInvite() const { return pendingGroupInvite; }
const std::string& getPendingInviterName() const { return pendingInviterName; }
// ---- Summon ----
bool hasPendingSummonRequest() const { return pendingSummonRequest_; }
const std::string& getSummonerName() const { return summonerName_; }
float getSummonTimeoutSec() const { return summonTimeoutSec_; }
void acceptSummon();
void declineSummon();
// ---- Trade ----
enum class TradeStatus : uint8_t {
None = 0, PendingIncoming, Open, Accepted, Complete
@ -1280,6 +1287,7 @@ private:
// ---- Instance lockout handler ----
void handleRaidInstanceInfo(network::Packet& packet);
void handleSummonRequest(network::Packet& packet);
void handleTradeStatus(network::Packet& packet);
void handleDuelRequested(network::Packet& packet);
void handleDuelComplete(network::Packet& packet);
@ -1638,6 +1646,12 @@ private:
bool pendingGroupInvite = false;
std::string pendingInviterName;
// Summon state
bool pendingSummonRequest_ = false;
uint64_t summonerGuid_ = 0;
std::string summonerName_;
float summonTimeoutSec_ = 0.0f;
// Trade state
TradeStatus tradeStatus_ = TradeStatus::None;
uint64_t tradePeerGuid_= 0;

View file

@ -207,6 +207,7 @@ private:
void renderDuelRequestPopup(game::GameHandler& gameHandler);
void renderLootRollPopup(game::GameHandler& gameHandler);
void renderTradeRequestPopup(game::GameHandler& gameHandler);
void renderSummonRequestPopup(game::GameHandler& gameHandler);
void renderBuffBar(game::GameHandler& gameHandler);
void renderLootWindow(game::GameHandler& gameHandler);
void renderGossipWindow(game::GameHandler& gameHandler);

View file

@ -1953,6 +1953,13 @@ void GameHandler::handlePacket(network::Packet& packet) {
case Opcode::SMSG_LOOT_REMOVED:
handleLootRemoved(packet);
break;
case Opcode::SMSG_SUMMON_REQUEST:
handleSummonRequest(packet);
break;
case Opcode::SMSG_SUMMON_CANCEL:
pendingSummonRequest_ = false;
addSystemChatMessage("Summon cancelled.");
break;
case Opcode::SMSG_TRADE_STATUS:
case Opcode::SMSG_TRADE_STATUS_EXTENDED:
handleTradeStatus(packet);
@ -14995,6 +15002,56 @@ void GameHandler::handleAuctionCommandResult(network::Packet& packet) {
" error=", result.errorCode);
}
// ---------------------------------------------------------------------------
// SMSG_SUMMON_REQUEST
// uint64 summonerGuid + uint32 zoneId + uint32 timeoutMs
// ---------------------------------------------------------------------------
void GameHandler::handleSummonRequest(network::Packet& packet) {
if (packet.getSize() - packet.getReadPos() < 16) return;
summonerGuid_ = packet.readUInt64();
/*uint32_t zoneId =*/ packet.readUInt32();
uint32_t timeoutMs = packet.readUInt32();
summonTimeoutSec_ = timeoutMs / 1000.0f;
pendingSummonRequest_= true;
summonerName_.clear();
auto entity = entityManager.getEntity(summonerGuid_);
if (auto* unit = dynamic_cast<Unit*>(entity.get())) {
summonerName_ = unit->getName();
}
if (summonerName_.empty()) {
char tmp[32];
std::snprintf(tmp, sizeof(tmp), "0x%llX",
static_cast<unsigned long long>(summonerGuid_));
summonerName_ = tmp;
}
addSystemChatMessage(summonerName_ + " is summoning you.");
LOG_INFO("SMSG_SUMMON_REQUEST: summoner=", summonerName_,
" timeout=", summonTimeoutSec_, "s");
}
void GameHandler::acceptSummon() {
if (!pendingSummonRequest_ || !socket) return;
pendingSummonRequest_ = false;
network::Packet pkt(wireOpcode(Opcode::CMSG_SUMMON_RESPONSE));
pkt.writeUInt8(1); // 1 = accept
socket->send(pkt);
addSystemChatMessage("Accepting summon...");
LOG_INFO("Accepted summon from ", summonerName_);
}
void GameHandler::declineSummon() {
if (!socket) return;
pendingSummonRequest_ = false;
network::Packet pkt(wireOpcode(Opcode::CMSG_SUMMON_RESPONSE));
pkt.writeUInt8(0); // 0 = decline
socket->send(pkt);
addSystemChatMessage("Summon declined.");
}
// ---------------------------------------------------------------------------
// Trade (SMSG_TRADE_STATUS / SMSG_TRADE_STATUS_EXTENDED)
// WotLK 3.3.5a status values:

View file

@ -399,6 +399,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
renderDuelRequestPopup(gameHandler);
renderLootRollPopup(gameHandler);
renderTradeRequestPopup(gameHandler);
renderSummonRequestPopup(gameHandler);
renderGuildInvitePopup(gameHandler);
renderGuildRoster(gameHandler);
renderBuffBar(gameHandler);
@ -4403,6 +4404,34 @@ void GameScreen::renderDuelRequestPopup(game::GameHandler& gameHandler) {
ImGui::End();
}
void GameScreen::renderSummonRequestPopup(game::GameHandler& gameHandler) {
if (!gameHandler.hasPendingSummonRequest()) return;
auto* window = core::Application::getInstance().getWindow();
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 175, 430), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(350, 0), ImGuiCond_Always);
if (ImGui::Begin("Summon Request", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize)) {
ImGui::Text("%s is summoning you.", gameHandler.getSummonerName().c_str());
float t = gameHandler.getSummonTimeoutSec();
if (t > 0.0f) {
ImGui::Text("Time remaining: %.0fs", t);
}
ImGui::Spacing();
if (ImGui::Button("Accept", ImVec2(130, 30))) {
gameHandler.acceptSummon();
}
ImGui::SameLine();
if (ImGui::Button("Decline", ImVec2(130, 30))) {
gameHandler.declineSummon();
}
}
ImGui::End();
}
void GameScreen::renderTradeRequestPopup(game::GameHandler& gameHandler) {
if (!gameHandler.hasPendingTradeRequest()) return;