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
|
||||
void submitGmTicket(const std::string& text);
|
||||
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 createGuild(const std::string& guildName);
|
||||
void addGuildRank(const std::string& rankName);
|
||||
|
|
@ -3021,6 +3028,12 @@ private:
|
|||
|
||||
// ---- Quest completion callback ----
|
||||
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
|
||||
|
|
|
|||
|
|
@ -438,7 +438,8 @@ private:
|
|||
void renderEquipSetWindow(game::GameHandler& gameHandler);
|
||||
|
||||
// GM Ticket window
|
||||
bool showGmTicketWindow_ = false;
|
||||
bool showGmTicketWindow_ = false;
|
||||
bool gmTicketWindowWasOpen_ = false; ///< Previous frame state; used to fire one-shot query
|
||||
char gmTicketBuf_[2048] = {};
|
||||
void renderGmTicketWindow(game::GameHandler& gameHandler);
|
||||
|
||||
|
|
|
|||
|
|
@ -5761,10 +5761,70 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_GMTICKET_GETTICKET:
|
||||
case Opcode::SMSG_GMTICKET_SYSTEMSTATUS:
|
||||
case Opcode::SMSG_GMTICKET_GETTICKET: {
|
||||
// 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());
|
||||
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 ----
|
||||
case Opcode::SMSG_CONVERT_RUNE: {
|
||||
|
|
@ -5975,7 +6035,33 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
packet.setReadPos(packet.getSize());
|
||||
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_SPELL_CHANCE_RESIST_PUSHBACK:
|
||||
case Opcode::SMSG_SPELL_UPDATE_CHAIN_TARGETS:
|
||||
|
|
@ -16153,9 +16239,19 @@ void GameHandler::deleteGmTicket() {
|
|||
if (state != WorldState::IN_WORLD || !socket) return;
|
||||
network::Packet pkt(wireOpcode(Opcode::CMSG_GMTICKET_DELETETICKET));
|
||||
socket->send(pkt);
|
||||
gmTicketActive_ = false;
|
||||
gmTicketText_.clear();
|
||||
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) {
|
||||
if (state != WorldState::IN_WORLD || !socket) return;
|
||||
auto packet = GuildQueryPacket::build(guildId);
|
||||
|
|
|
|||
|
|
@ -20162,9 +20162,15 @@ void GameScreen::renderAchievementWindow(game::GameHandler& gameHandler) {
|
|||
|
||||
// ─── GM Ticket Window ─────────────────────────────────────────────────────────
|
||||
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;
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(400, 260), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(440, 320), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowPos(ImVec2(300, 200), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (!ImGui::Begin("GM Ticket", &showGmTicketWindow_,
|
||||
|
|
@ -20173,10 +20179,33 @@ void GameScreen::renderGmTicketWindow(game::GameHandler& gameHandler) {
|
|||
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::Spacing();
|
||||
ImGui::InputTextMultiline("##gmticket_body", gmTicketBuf_, sizeof(gmTicketBuf_),
|
||||
ImVec2(-1, 160));
|
||||
ImVec2(-1, 120));
|
||||
ImGui::Spacing();
|
||||
|
||||
bool hasText = (gmTicketBuf_[0] != '\0');
|
||||
|
|
@ -20193,8 +20222,11 @@ void GameScreen::renderGmTicketWindow(game::GameHandler& gameHandler) {
|
|||
showGmTicketWindow_ = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Delete Ticket", ImVec2(100, 0))) {
|
||||
gameHandler.deleteGmTicket();
|
||||
if (gameHandler.hasActiveGmTicket()) {
|
||||
if (ImGui::Button("Delete Ticket", ImVec2(110, 0))) {
|
||||
gameHandler.deleteGmTicket();
|
||||
showGmTicketWindow_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue