Skip to content

Commit cd913fe

Browse files
authored
Merge f5cb2d2 into 9b25567
2 parents 9b25567 + f5cb2d2 commit cd913fe

3 files changed

Lines changed: 227 additions & 0 deletions

File tree

src/game/ChatCommands/GMTicketCommands.cpp

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@
3434

3535
#include "Chat.h"
3636
#include "ObjectMgr.h"
37+
#include "Opcodes.h"
3738
#include "World.h"
39+
#include "WorldPacket.h"
3840
#include "GMTicketMgr.h"
3941
#include "Mail.h"
4042

43+
#include <iomanip>
44+
4145
// show ticket (helper)
4246
void ChatHandler::ShowTicket(GMTicket const* ticket)
4347
{
@@ -673,3 +677,211 @@ bool ChatHandler::HandleTickerSurveyClose(char* args)
673677

674678
return true;
675679
}
680+
681+
static std::string TicketEscapeField(const std::string& in)
682+
{
683+
std::string out;
684+
out.reserve(in.size());
685+
for (size_t i = 0; i < in.size(); ++i)
686+
{
687+
char c = in[i];
688+
switch (c)
689+
{
690+
case '\\': out += "\\\\"; break;
691+
case '\t': out += "\\t"; break;
692+
case '\n': out += "\\n"; break;
693+
case '\r': out += "\\r"; break;
694+
default: out += c;
695+
}
696+
}
697+
return out;
698+
}
699+
700+
void ChatHandler::SendTicketPayload(char kind, const std::string& body)
701+
{
702+
if (!m_session || !m_session->GetPlayer())
703+
{
704+
return;
705+
}
706+
707+
const size_t CHUNK = 200;
708+
const size_t MAXCHUNKS = 16;
709+
710+
std::string b = body;
711+
if (b.size() > CHUNK * MAXCHUNKS)
712+
{
713+
b.resize(CHUNK * MAXCHUNKS);
714+
}
715+
716+
size_t total = b.empty() ? 1 : ((b.size() + CHUNK - 1) / CHUNK);
717+
ObjectGuid sender = m_session->GetPlayer()->GetObjectGuid();
718+
719+
for (size_t seq = 1; seq <= total; ++seq)
720+
{
721+
std::string chunk = b.empty() ? std::string() : b.substr((seq - 1) * CHUNK, CHUNK);
722+
std::ostringstream framed;
723+
framed << "ZGMTKT\t" << kind << "\t" << seq << "\t" << total << "\t" << chunk;
724+
std::string msg = framed.str();
725+
726+
WorldPacket data(SMSG_MESSAGECHAT, msg.size() + 32);
727+
data << uint8(CHAT_MSG_WHISPER);
728+
data << int32(LANG_ADDON);
729+
data << sender;
730+
data << uint32(msg.length() + 1);
731+
data << msg;
732+
data << uint8(0);
733+
m_session->SendPacket(&data);
734+
}
735+
}
736+
737+
bool ChatHandler::HandleTicketPayloadPingCommand(char* /*args*/)
738+
{
739+
// Stress the whisper channel exactly where real payloads are fragile:
740+
// a 2-chunk body whose 200-byte boundary lands on a space (chunk 1 ends in a
741+
// space) and whose last chunk ends in a trailing space, plus an empty-body 'L'
742+
// frame (empty final chunk). The spike confirms these survive intact.
743+
std::string body(199, 'a');
744+
body += ' '; // byte 200 -> chunk 1 ends in a space
745+
body += std::string(199, 'b');
746+
body += ' '; // trailing space on the final chunk
747+
SendTicketPayload('P', body);
748+
SendTicketPayload('L', ""); // empty-body frame (empty chunk, total=1)
749+
return true;
750+
}
751+
752+
bool ChatHandler::HandleTicketPayloadShowCommand(char* args)
753+
{
754+
char* px = ExtractLiteralArg(&args);
755+
if (!px)
756+
{
757+
return false;
758+
}
759+
760+
GMTicket* ticket = NULL;
761+
uint32 num;
762+
if (ExtractUInt32(&px, num))
763+
{
764+
if (num == 0)
765+
{
766+
return false;
767+
}
768+
ticket = sTicketMgr.GetGMTicket(num);
769+
if (!ticket)
770+
{
771+
PSendSysMessage(LANG_COMMAND_TICKETNOTEXIST, num);
772+
SetSentErrorMessage(true);
773+
return false;
774+
}
775+
}
776+
else
777+
{
778+
ObjectGuid target_guid; std::string target_name;
779+
if (!ExtractPlayerTarget(&px, NULL, &target_guid, &target_name))
780+
{
781+
return false;
782+
}
783+
ticket = sTicketMgr.GetGMTicket(target_guid);
784+
if (!ticket)
785+
{
786+
PSendSysMessage(LANG_COMMAND_TICKETNOTEXIST_NAME, target_name.c_str());
787+
SetSentErrorMessage(true);
788+
return false;
789+
}
790+
}
791+
792+
std::string name; bool online; float x, y, z; uint32 mapId;
793+
bool posValid = ResolveTicketCreator(ticket->GetPlayerGuid(), name, online, x, y, z, mapId);
794+
795+
std::string text = ticket->GetText();
796+
if (text.size() > 500)
797+
{
798+
text.resize(500);
799+
}
800+
std::string resp = ticket->GetResponse();
801+
if (resp.size() > 500)
802+
{
803+
resp.resize(500);
804+
}
805+
806+
std::ostringstream b;
807+
b << ticket->GetId() << "\t" << TicketEscapeField(name) << "\t" << (online ? "1" : "0")
808+
<< "\t" << (posValid ? "1" : "0")
809+
<< "\t" << std::fixed << std::setprecision(2) << x << "\t" << y << "\t" << z
810+
<< "\t" << mapId << "\t" << TicketEscapeField(text) << "\t" << TicketEscapeField(resp);
811+
SendTicketPayload('D', b.str());
812+
return true;
813+
}
814+
815+
bool ChatHandler::ResolveTicketCreator(ObjectGuid guid, std::string& name, bool& online,
816+
float& x, float& y, float& z, uint32& mapId)
817+
{
818+
if (Player* p = sObjectMgr.GetPlayer(guid))
819+
{
820+
online = true;
821+
name = p->GetName();
822+
x = p->GetPositionX(); y = p->GetPositionY(); z = p->GetPositionZ();
823+
mapId = p->GetMapId();
824+
return true;
825+
}
826+
827+
online = false; x = 0.0f; y = 0.0f; z = 0.0f; mapId = 0; name = "<offline>";
828+
QueryResult* res = CharacterDatabase.PQuery(
829+
"SELECT `name`, `position_x`, `position_y`, `position_z`, `map` "
830+
"FROM `characters` WHERE `guid` = '%u'", guid.GetCounter());
831+
if (res)
832+
{
833+
Field* fld = res->Fetch();
834+
name = fld[0].GetCppString();
835+
x = fld[1].GetFloat(); y = fld[2].GetFloat(); z = fld[3].GetFloat();
836+
mapId = fld[4].GetUInt32();
837+
delete res;
838+
return true;
839+
}
840+
return false;
841+
}
842+
843+
bool ChatHandler::HandleTicketPayloadListCommand(char* /*args*/)
844+
{
845+
std::string body;
846+
// Cap like HandleTicketListCommand so the joined body stays well under 200*16 bytes;
847+
// an uncapped body would hit the SendTicketPayload cap and truncate the last record.
848+
uint16 numToShow = std::min(uint16(sTicketMgr.GetTicketCount()),
849+
uint16(sWorld.getConfig(CONFIG_UINT32_GM_TICKET_LIST_SIZE)));
850+
for (uint16 i = 0; i < numToShow; ++i)
851+
{
852+
GMTicket* t = sTicketMgr.GetGMTicketByOrderPos(i);
853+
if (!t)
854+
{
855+
continue;
856+
}
857+
858+
std::string name; bool online; float x, y, z; uint32 mapId;
859+
ResolveTicketCreator(t->GetPlayerGuid(), name, online, x, y, z, mapId);
860+
861+
std::string snippet = t->GetText();
862+
if (snippet.size() > 40)
863+
{
864+
snippet.resize(40);
865+
}
866+
867+
uint32 age = uint32(time(NULL) - t->GetLastUpdate());
868+
std::ostringstream rec;
869+
rec << t->GetId() << "\t" << TicketEscapeField(name) << "\t" << age << "\t"
870+
<< (online ? "1" : "0") << "\t" << TicketEscapeField(snippet);
871+
std::string recStr = rec.str();
872+
// Bound the joined body by BYTES, not only record count: SendTicketPayload caps at
873+
// 200*16=3200 bytes and would resize()-truncate the tail record into an unparseable
874+
// (<5-field) fragment that the addon drops. Stop cleanly before that can happen.
875+
if (!body.empty() && body.size() + 1 + recStr.size() > 3100)
876+
{
877+
break;
878+
}
879+
if (!body.empty())
880+
{
881+
body += "\n";
882+
}
883+
body += recStr;
884+
}
885+
SendTicketPayload('L', body);
886+
return true;
887+
}

src/game/WorldHandlers/Chat.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,14 @@ ChatCommand* ChatHandler::getCommandTable()
726726
{ NULL, 0, false, NULL, "", NULL }
727727
};
728728

729+
static ChatCommand ticketPayloadCommandTable[] =
730+
{
731+
{ "list", SEC_GAMEMASTER, true, &ChatHandler::HandleTicketPayloadListCommand, "", NULL },
732+
{ "show", SEC_GAMEMASTER, true, &ChatHandler::HandleTicketPayloadShowCommand, "", NULL },
733+
{ "ping", SEC_GAMEMASTER, true, &ChatHandler::HandleTicketPayloadPingCommand, "", NULL },
734+
{ NULL, 0, false, NULL, "", NULL }
735+
};
736+
729737
static ChatCommand ticketCommandTable[] =
730738
{
731739
{ "accept", SEC_ADMINISTRATOR, true, &ChatHandler::HandleTicketAcceptCommand, "", NULL },
@@ -735,6 +743,7 @@ ChatCommand* ChatHandler::getCommandTable()
735743
{ "list", SEC_GAMEMASTER, true, &ChatHandler::HandleTicketListCommand, "", NULL },
736744
{ "meaccept", SEC_GAMEMASTER, false, &ChatHandler::HandleTicketMeAcceptCommand, "", NULL },
737745
{ "onlinelist", SEC_GAMEMASTER, true, &ChatHandler::HandleTicketOnlineListCommand, "", NULL },
746+
{ "payload", SEC_GAMEMASTER, true, NULL, "", ticketPayloadCommandTable },
738747
{ "respond", SEC_GAMEMASTER, true, &ChatHandler::HandleTicketRespondCommand, "", NULL },
739748
{ "show", SEC_GAMEMASTER, true, &ChatHandler::HandleTicketShowCommand, "", NULL },
740749
{ "surveyclose", SEC_GAMEMASTER, true, &ChatHandler::HandleTickerSurveyClose, "", NULL },

src/game/WorldHandlers/Chat.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,10 @@ class ChatHandler
685685
bool HandleTicketShowCommand(char* args);
686686
bool HandleTickerSurveyClose(char* args);
687687

688+
bool HandleTicketPayloadListCommand(char* args);
689+
bool HandleTicketPayloadShowCommand(char* args);
690+
bool HandleTicketPayloadPingCommand(char* args);
691+
688692
bool HandleMaxSkillCommand(char* args);
689693
bool HandleSetSkillCommand(char* args);
690694
bool HandleRespawnCommand(char* args);
@@ -772,6 +776,8 @@ class ChatHandler
772776
void ShowSpellListHelper(Player* target, SpellEntry const* spellInfo, LocaleConstant loc);
773777
void ShowPoolListHelper(uint16 pool_id);
774778
void ShowTicket(GMTicket const* ticket);
779+
void SendTicketPayload(char kind, const std::string& body);
780+
bool ResolveTicketCreator(ObjectGuid guid, std::string& name, bool& online, float& x, float& y, float& z, uint32& mapId);
775781
void ShowTriggerListHelper(AreaTriggerEntry const* atEntry);
776782
void ShowTriggerTargetListHelper(uint32 id, AreaTrigger const* at, bool subpart = false);
777783
bool LookupPlayerSearchCommand(QueryResult* result, uint32* limit = NULL);

0 commit comments

Comments
 (0)