mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
feat: parse SMSG_GMTICKET_GETTICKET/SYSTEMSTATUS and SMSG_SPELLINSTAKILLLOG
Previously SMSG_GMTICKET_GETTICKET and SMSG_GMTICKET_SYSTEMSTATUS were silently consumed. Now both are fully parsed: - SMSG_GMTICKET_GETTICKET decodes all four status codes (no ticket, open ticket, closed, suspended), extracts ticket text, age and server-estimated wait time, and stores them on GameHandler. - SMSG_GMTICKET_SYSTEMSTATUS shows a chat message when GM support goes offline/online. - Added requestGmTicket() (sends CMSG_GMTICKET_GETTICKET) called automatically when the GM Ticket UI window is opened, so the player sees their existing open ticket text and wait time on first open. - GM Ticket UI window now shows current-ticket status bar, estimated wait time, and hides the Delete button when no ticket is active. Also implements SMSG_SPELLINSTAKILLLOG (previously silently consumed): parses caster/victim/spellId for all expansions and emits combat text when the local player is involved in an instant-kill spell event (e.g. Execute, Obliterate).
This commit is contained in:
parent
9b60108fa6
commit
dd38026b23
4 changed files with 150 additions and 8 deletions
|
|
@ -494,6 +494,13 @@ public:
|
||||||
// GM Ticket
|
// GM Ticket
|
||||||
void submitGmTicket(const std::string& text);
|
void submitGmTicket(const std::string& text);
|
||||||
void deleteGmTicket();
|
void deleteGmTicket();
|
||||||
|
void requestGmTicket(); ///< Send CMSG_GMTICKET_GETTICKET to query open ticket
|
||||||
|
|
||||||
|
// GM ticket status accessors
|
||||||
|
bool hasActiveGmTicket() const { return gmTicketActive_; }
|
||||||
|
const std::string& getGmTicketText() const { return gmTicketText_; }
|
||||||
|
bool isGmSupportAvailable() const { return gmSupportAvailable_; }
|
||||||
|
float getGmTicketWaitHours() const { return gmTicketWaitHours_; }
|
||||||
void queryGuildInfo(uint32_t guildId);
|
void queryGuildInfo(uint32_t guildId);
|
||||||
void createGuild(const std::string& guildName);
|
void createGuild(const std::string& guildName);
|
||||||
void addGuildRank(const std::string& rankName);
|
void addGuildRank(const std::string& rankName);
|
||||||
|
|
@ -3021,6 +3028,12 @@ private:
|
||||||
|
|
||||||
// ---- Quest completion callback ----
|
// ---- Quest completion callback ----
|
||||||
QuestCompleteCallback questCompleteCallback_;
|
QuestCompleteCallback questCompleteCallback_;
|
||||||
|
|
||||||
|
// ---- GM Ticket state (SMSG_GMTICKET_GETTICKET / SMSG_GMTICKET_SYSTEMSTATUS) ----
|
||||||
|
bool gmTicketActive_ = false; ///< True when an open ticket exists on the server
|
||||||
|
std::string gmTicketText_; ///< Text of the open ticket (from SMSG_GMTICKET_GETTICKET)
|
||||||
|
float gmTicketWaitHours_ = 0.0f; ///< Server-estimated wait time in hours
|
||||||
|
bool gmSupportAvailable_ = true; ///< GM support system online (SMSG_GMTICKET_SYSTEMSTATUS)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace game
|
} // namespace game
|
||||||
|
|
|
||||||
|
|
@ -438,7 +438,8 @@ private:
|
||||||
void renderEquipSetWindow(game::GameHandler& gameHandler);
|
void renderEquipSetWindow(game::GameHandler& gameHandler);
|
||||||
|
|
||||||
// GM Ticket window
|
// GM Ticket window
|
||||||
bool showGmTicketWindow_ = false;
|
bool showGmTicketWindow_ = false;
|
||||||
|
bool gmTicketWindowWasOpen_ = false; ///< Previous frame state; used to fire one-shot query
|
||||||
char gmTicketBuf_[2048] = {};
|
char gmTicketBuf_[2048] = {};
|
||||||
void renderGmTicketWindow(game::GameHandler& gameHandler);
|
void renderGmTicketWindow(game::GameHandler& gameHandler);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5761,10 +5761,70 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Opcode::SMSG_GMTICKET_GETTICKET:
|
case Opcode::SMSG_GMTICKET_GETTICKET: {
|
||||||
case Opcode::SMSG_GMTICKET_SYSTEMSTATUS:
|
// WotLK 3.3.5a format:
|
||||||
|
// uint8 status — 1=no ticket, 6=has open ticket, 3=closed, 10=suspended
|
||||||
|
// If status == 6 (GMTICKET_STATUS_HASTEXT):
|
||||||
|
// cstring ticketText
|
||||||
|
// uint32 ticketAge (seconds old)
|
||||||
|
// uint32 daysUntilOld (days remaining before escalation)
|
||||||
|
// float waitTimeHours (estimated GM wait time)
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 1) { packet.setReadPos(packet.getSize()); break; }
|
||||||
|
uint8_t gmStatus = packet.readUInt8();
|
||||||
|
// Status 6 = GMTICKET_STATUS_HASTEXT — open ticket with text
|
||||||
|
if (gmStatus == 6 && packet.getSize() - packet.getReadPos() >= 1) {
|
||||||
|
gmTicketText_ = packet.readString();
|
||||||
|
uint32_t ageSec = (packet.getSize() - packet.getReadPos() >= 4) ? packet.readUInt32() : 0;
|
||||||
|
/*uint32_t daysLeft =*/ (packet.getSize() - packet.getReadPos() >= 4) ? packet.readUInt32() : 0;
|
||||||
|
gmTicketWaitHours_ = (packet.getSize() - packet.getReadPos() >= 4)
|
||||||
|
? packet.readFloat() : 0.0f;
|
||||||
|
gmTicketActive_ = true;
|
||||||
|
char buf[256];
|
||||||
|
if (ageSec < 60) {
|
||||||
|
std::snprintf(buf, sizeof(buf),
|
||||||
|
"You have an open GM ticket (submitted %us ago). Estimated wait: %.1f hours.",
|
||||||
|
ageSec, gmTicketWaitHours_);
|
||||||
|
} else {
|
||||||
|
uint32_t ageMin = ageSec / 60;
|
||||||
|
std::snprintf(buf, sizeof(buf),
|
||||||
|
"You have an open GM ticket (submitted %um ago). Estimated wait: %.1f hours.",
|
||||||
|
ageMin, gmTicketWaitHours_);
|
||||||
|
}
|
||||||
|
addSystemChatMessage(buf);
|
||||||
|
LOG_INFO("SMSG_GMTICKET_GETTICKET: open ticket age=", ageSec,
|
||||||
|
"s wait=", gmTicketWaitHours_, "h");
|
||||||
|
} else if (gmStatus == 3) {
|
||||||
|
gmTicketActive_ = false;
|
||||||
|
gmTicketText_.clear();
|
||||||
|
addSystemChatMessage("Your GM ticket has been closed.");
|
||||||
|
LOG_INFO("SMSG_GMTICKET_GETTICKET: ticket closed");
|
||||||
|
} else if (gmStatus == 10) {
|
||||||
|
gmTicketActive_ = false;
|
||||||
|
gmTicketText_.clear();
|
||||||
|
addSystemChatMessage("Your GM ticket has been suspended.");
|
||||||
|
LOG_INFO("SMSG_GMTICKET_GETTICKET: ticket suspended");
|
||||||
|
} else {
|
||||||
|
// Status 1 = no open ticket (default/no ticket)
|
||||||
|
gmTicketActive_ = false;
|
||||||
|
gmTicketText_.clear();
|
||||||
|
LOG_DEBUG("SMSG_GMTICKET_GETTICKET: no open ticket (status=", (int)gmStatus, ")");
|
||||||
|
}
|
||||||
packet.setReadPos(packet.getSize());
|
packet.setReadPos(packet.getSize());
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case Opcode::SMSG_GMTICKET_SYSTEMSTATUS: {
|
||||||
|
// uint32 status: 1 = GM support available, 0 = offline/unavailable
|
||||||
|
if (packet.getSize() - packet.getReadPos() >= 4) {
|
||||||
|
uint32_t sysStatus = packet.readUInt32();
|
||||||
|
gmSupportAvailable_ = (sysStatus != 0);
|
||||||
|
addSystemChatMessage(gmSupportAvailable_
|
||||||
|
? "GM support is currently available."
|
||||||
|
: "GM support is currently unavailable.");
|
||||||
|
LOG_INFO("SMSG_GMTICKET_SYSTEMSTATUS: available=", gmSupportAvailable_);
|
||||||
|
}
|
||||||
|
packet.setReadPos(packet.getSize());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- DK rune tracking ----
|
// ---- DK rune tracking ----
|
||||||
case Opcode::SMSG_CONVERT_RUNE: {
|
case Opcode::SMSG_CONVERT_RUNE: {
|
||||||
|
|
@ -5975,7 +6035,33 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
packet.setReadPos(packet.getSize());
|
packet.setReadPos(packet.getSize());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Opcode::SMSG_SPELLINSTAKILLLOG:
|
case Opcode::SMSG_SPELLINSTAKILLLOG: {
|
||||||
|
// Sent when a unit is killed by a spell with SPELL_ATTR_EX2_INSTAKILL (e.g. Execute, Obliterate, etc.)
|
||||||
|
// WotLK: packed_guid caster + packed_guid victim + uint32 spellId
|
||||||
|
// TBC/Classic: full uint64 caster + full uint64 victim + uint32 spellId
|
||||||
|
const bool ikTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||||
|
auto ik_rem = [&]() { return packet.getSize() - packet.getReadPos(); };
|
||||||
|
if (ik_rem() < (ikTbcLike ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; }
|
||||||
|
uint64_t ikCaster = ikTbcLike
|
||||||
|
? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet);
|
||||||
|
if (ik_rem() < (ikTbcLike ? 8u : 1u)) { packet.setReadPos(packet.getSize()); break; }
|
||||||
|
uint64_t ikVictim = ikTbcLike
|
||||||
|
? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet);
|
||||||
|
uint32_t ikSpell = (ik_rem() >= 4) ? packet.readUInt32() : 0;
|
||||||
|
// Show kill/death feedback for the local player
|
||||||
|
if (ikCaster == playerGuid) {
|
||||||
|
// We killed a target instantly — show a KILL combat text hit
|
||||||
|
addCombatText(CombatTextEntry::MELEE_DAMAGE, 0, ikSpell, true);
|
||||||
|
} else if (ikVictim == playerGuid) {
|
||||||
|
// We were instantly killed — show a large incoming hit
|
||||||
|
addCombatText(CombatTextEntry::MELEE_DAMAGE, 0, ikSpell, false);
|
||||||
|
addSystemChatMessage("You were killed by an instant-kill effect.");
|
||||||
|
}
|
||||||
|
LOG_DEBUG("SMSG_SPELLINSTAKILLLOG: caster=0x", std::hex, ikCaster,
|
||||||
|
" victim=0x", ikVictim, std::dec, " spell=", ikSpell);
|
||||||
|
packet.setReadPos(packet.getSize());
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Opcode::SMSG_SPELLLOGEXECUTE:
|
case Opcode::SMSG_SPELLLOGEXECUTE:
|
||||||
case Opcode::SMSG_SPELL_CHANCE_RESIST_PUSHBACK:
|
case Opcode::SMSG_SPELL_CHANCE_RESIST_PUSHBACK:
|
||||||
case Opcode::SMSG_SPELL_UPDATE_CHAIN_TARGETS:
|
case Opcode::SMSG_SPELL_UPDATE_CHAIN_TARGETS:
|
||||||
|
|
@ -16153,9 +16239,19 @@ void GameHandler::deleteGmTicket() {
|
||||||
if (state != WorldState::IN_WORLD || !socket) return;
|
if (state != WorldState::IN_WORLD || !socket) return;
|
||||||
network::Packet pkt(wireOpcode(Opcode::CMSG_GMTICKET_DELETETICKET));
|
network::Packet pkt(wireOpcode(Opcode::CMSG_GMTICKET_DELETETICKET));
|
||||||
socket->send(pkt);
|
socket->send(pkt);
|
||||||
|
gmTicketActive_ = false;
|
||||||
|
gmTicketText_.clear();
|
||||||
LOG_INFO("Deleting GM ticket");
|
LOG_INFO("Deleting GM ticket");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameHandler::requestGmTicket() {
|
||||||
|
if (state != WorldState::IN_WORLD || !socket) return;
|
||||||
|
// CMSG_GMTICKET_GETTICKET has no payload — server responds with SMSG_GMTICKET_GETTICKET
|
||||||
|
network::Packet pkt(wireOpcode(Opcode::CMSG_GMTICKET_GETTICKET));
|
||||||
|
socket->send(pkt);
|
||||||
|
LOG_DEBUG("Sent CMSG_GMTICKET_GETTICKET — querying open ticket status");
|
||||||
|
}
|
||||||
|
|
||||||
void GameHandler::queryGuildInfo(uint32_t guildId) {
|
void GameHandler::queryGuildInfo(uint32_t guildId) {
|
||||||
if (state != WorldState::IN_WORLD || !socket) return;
|
if (state != WorldState::IN_WORLD || !socket) return;
|
||||||
auto packet = GuildQueryPacket::build(guildId);
|
auto packet = GuildQueryPacket::build(guildId);
|
||||||
|
|
|
||||||
|
|
@ -20162,9 +20162,15 @@ void GameScreen::renderAchievementWindow(game::GameHandler& gameHandler) {
|
||||||
|
|
||||||
// ─── GM Ticket Window ─────────────────────────────────────────────────────────
|
// ─── GM Ticket Window ─────────────────────────────────────────────────────────
|
||||||
void GameScreen::renderGmTicketWindow(game::GameHandler& gameHandler) {
|
void GameScreen::renderGmTicketWindow(game::GameHandler& gameHandler) {
|
||||||
|
// Fire a one-shot query when the window first becomes visible
|
||||||
|
if (showGmTicketWindow_ && !gmTicketWindowWasOpen_) {
|
||||||
|
gameHandler.requestGmTicket();
|
||||||
|
}
|
||||||
|
gmTicketWindowWasOpen_ = showGmTicketWindow_;
|
||||||
|
|
||||||
if (!showGmTicketWindow_) return;
|
if (!showGmTicketWindow_) return;
|
||||||
|
|
||||||
ImGui::SetNextWindowSize(ImVec2(400, 260), ImGuiCond_FirstUseEver);
|
ImGui::SetNextWindowSize(ImVec2(440, 320), ImGuiCond_FirstUseEver);
|
||||||
ImGui::SetNextWindowPos(ImVec2(300, 200), ImGuiCond_FirstUseEver);
|
ImGui::SetNextWindowPos(ImVec2(300, 200), ImGuiCond_FirstUseEver);
|
||||||
|
|
||||||
if (!ImGui::Begin("GM Ticket", &showGmTicketWindow_,
|
if (!ImGui::Begin("GM Ticket", &showGmTicketWindow_,
|
||||||
|
|
@ -20173,10 +20179,33 @@ void GameScreen::renderGmTicketWindow(game::GameHandler& gameHandler) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show GM support availability
|
||||||
|
if (!gameHandler.isGmSupportAvailable()) {
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "GM support is currently unavailable.");
|
||||||
|
ImGui::Spacing();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show existing open ticket if any
|
||||||
|
if (gameHandler.hasActiveGmTicket()) {
|
||||||
|
ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "You have an open GM ticket.");
|
||||||
|
const std::string& existingText = gameHandler.getGmTicketText();
|
||||||
|
if (!existingText.empty()) {
|
||||||
|
ImGui::TextWrapped("Current ticket: %s", existingText.c_str());
|
||||||
|
}
|
||||||
|
float waitHours = gameHandler.getGmTicketWaitHours();
|
||||||
|
if (waitHours > 0.0f) {
|
||||||
|
char waitBuf[64];
|
||||||
|
std::snprintf(waitBuf, sizeof(waitBuf), "Estimated wait: %.1f hours", waitHours);
|
||||||
|
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.4f, 1.0f), "%s", waitBuf);
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Spacing();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::TextWrapped("Describe your issue and a Game Master will contact you.");
|
ImGui::TextWrapped("Describe your issue and a Game Master will contact you.");
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::InputTextMultiline("##gmticket_body", gmTicketBuf_, sizeof(gmTicketBuf_),
|
ImGui::InputTextMultiline("##gmticket_body", gmTicketBuf_, sizeof(gmTicketBuf_),
|
||||||
ImVec2(-1, 160));
|
ImVec2(-1, 120));
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
||||||
bool hasText = (gmTicketBuf_[0] != '\0');
|
bool hasText = (gmTicketBuf_[0] != '\0');
|
||||||
|
|
@ -20193,8 +20222,11 @@ void GameScreen::renderGmTicketWindow(game::GameHandler& gameHandler) {
|
||||||
showGmTicketWindow_ = false;
|
showGmTicketWindow_ = false;
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Delete Ticket", ImVec2(100, 0))) {
|
if (gameHandler.hasActiveGmTicket()) {
|
||||||
gameHandler.deleteGmTicket();
|
if (ImGui::Button("Delete Ticket", ImVec2(110, 0))) {
|
||||||
|
gameHandler.deleteGmTicket();
|
||||||
|
showGmTicketWindow_ = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue