Skip to content

Commit

Permalink
Gm ticket handling fixes (#90)
Browse files Browse the repository at this point in the history
* Fix .ticket response & .ticket close handling

- Now the GMTicket::SetResponseText method will save the response to the right ticket and will nor update all ticket responses for the same character
- Fix potential issue in GMTicketMgr::Create : limiting to the the most recent ticket of the player and avoid potential multiple returns if there is inconsistent data in table
- Fix GMTicket::SetText
- Prevent to update ticket_text for old tickets of the same char when submiting ticket or updating ticket text in game
- Fix item_text GUID generation
- Setting system response when ticket closed without mail response
- Create a PSendSysMessageMultiline function , will split a mangos string in multiple lines if "@@" string is found.

* Fix revision.h after structure change

Need apply Rel21_16_053_GM_tickets_handling_fixes.sql in order to work correctly
  • Loading branch information
Elmsroth authored May 11, 2020
1 parent 7a749e0 commit 778941c
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 26 deletions.
90 changes: 82 additions & 8 deletions src/game/ChatCommands/Level2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "GMTicketMgr.h"
#include "WaypointManager.h"
#include "DBCStores.h"
#include "Mail.h"
#include "Util.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
Expand Down Expand Up @@ -2852,20 +2853,46 @@ bool ChatHandler::HandleTicketCloseCommand(char* args)
}
}

Player* pPlayer = sObjectMgr.GetPlayer(ticket->GetPlayerGuid());
ObjectGuid target_guid = ticket->GetPlayerGuid();

// Get Player
// Can be nullptr if player is offline
Player* pPlayer = sObjectMgr.GetPlayer(target_guid);

// Get Player name
std::string target_name;
sObjectMgr.GetPlayerNameByGUID(target_guid, target_name);


if (!pPlayer && !sWorld.getConfig(CONFIG_BOOL_GM_TICKET_OFFLINE_CLOSING))
{
SendSysMessage(LANG_COMMAND_TICKET_CANT_CLOSE);
return false;
}

// Set reponse text if not existing
if (!*ticket->GetResponse())
{
const char* format = "[System Message] This ticket was closed by <GM>%s without any mail response, perhaps it was resolved by direct chat.";
const uint32 responseBufferSize = 256;
char response[responseBufferSize];
const char* buffer;
snprintf(response, responseBufferSize, format, m_session->GetPlayer()->GetName());
ticket->SetResponseText(response);
}

ticket->Close();

// Define ticketId variable because we need ticket id after deleting it from TicketMgr
uint32 ticketId = ticket->GetId();

//This logic feels misplaced, but you can't have it in GMTicket?
sTicketMgr.Delete(ticket->GetPlayerGuid()); // here, ticket become invalidated and should not be used below

PSendSysMessage(LANG_COMMAND_TICKETCLOSED_NAME, pPlayer ? pPlayer->GetName() : "an offline player");
// Send system message to current GM
PSendSysMessage(LANG_COMMAND_TICKETCLOSED_NAME, ticketId, target_name.c_str(), m_session->GetPlayer()->GetName());

// TODO : Send system Message to All Connected GMs to informe them the ticket has been closed

return true;
}
Expand Down Expand Up @@ -3085,17 +3112,64 @@ bool ChatHandler::HandleTicketRespondCommand(char* args)
return false;
}

// Set the response text to the ticket
ticket->SetResponseText(args);

if (Player* pl = sObjectMgr.GetPlayer(ticket->GetPlayerGuid()))
// Send in-game email with ticket answer
MailDraft draft;

const char * signatureFormat = GetMangosString(LANG_COMMAND_TICKET_RESPOND_MAIL_SIGNATURE);
const uint32 signatureBufferSize = 256;
char signature[signatureBufferSize];
snprintf(signature, signatureBufferSize, signatureFormat, m_session->GetPlayer()->GetName());

std::string mailText = args ;
mailText = mailText + signature;

draft.SetSubjectAndBody(GetMangosString(LANG_COMMAND_TICKET_RESPOND_MAIL_SUBJECT), mailText);

MailSender sender(MAIL_NORMAL, m_session->GetPlayer()->GetGUIDLow(), MAIL_STATIONERY_GM);

ObjectGuid target_guid = ticket->GetPlayerGuid();

// Get Player
// Can be nullptr if player is offline
Player* target = sObjectMgr.GetPlayer(target_guid);

// Get Player name
std::string target_name;
sObjectMgr.GetPlayerNameByGUID(target_guid, target_name);

// Find player to send, hopefully we have his guid if target is nullpt
// Todo set MailDraft sent by GM and handle 90 day delay
draft.SendMailTo(MailReceiver(target, target_guid), sender);

// If player is online, notify with a system message that the ticket was handled.
if (target && target->IsInWorld())
{
ChatHandler(target).PSendSysMessageMultiline(LANG_COMMAND_TICKETCLOSED_PLAYER_NOTIF, m_session->GetPlayer()->GetName());
}

// Define ticketId variable because we need ticket id after deleting it from TicketMgr in notification formated string
uint32 ticketId = ticket->GetId();

// Close the ticket
ticket->Close();


// Remove ticket from ticket manager
// Otherwise ticket will reappear in player UI if teleported or logout/login !
sTicketMgr.Delete(ticket->GetPlayerGuid());

// Send system Message to All Connected GMs to informe them the ticket has been closed
sObjectAccessor.DoForAllPlayers([&](Player* player)
{
pl->GetSession()->SendGMTicketGetTicket(0x06, ticket);
//How should we error here?
if (m_session)
if (player->GetSession()->GetSecurity() >= SEC_GAMEMASTER && player->isAcceptTickets())
{
m_session->GetPlayer()->Whisper(args, LANG_UNIVERSAL, pl->GetObjectGuid());
ChatHandler(player).PSendSysMessage(LANG_COMMAND_TICKETCLOSED_NAME, ticketId, target_name.c_str(), m_session->GetPlayer()->GetName());
}
}
});


return true;
}
Expand Down
29 changes: 18 additions & 11 deletions src/game/Object/GMTicketMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,25 @@ void GMTicket::SetText(const char* text)
std::string escapedString = m_text;
CharacterDatabase.escape_string(escapedString);
CharacterDatabase.PExecute("UPDATE `character_ticket` SET `ticket_text` = '%s' "
"WHERE `guid` = '%u'",
escapedString.c_str(), m_guid.GetCounter());
"WHERE `guid` = '%u' AND `ticket_id` = %u",
escapedString.c_str(), m_guid.GetCounter(), m_ticketId);
}

void GMTicket::SetResponseText(const char* text)
{
m_responseText = text ? text : "";
m_lastUpdate = time(NULL);

std::string escapedString = m_responseText;
CharacterDatabase.escape_string(escapedString);
CharacterDatabase.PExecute("UPDATE `character_ticket` SET `response_text` = '%s' "
"WHERE `guid` = '%u'",
escapedString.c_str(), m_guid.GetCounter());

// Perform action in DB only if text is not empty
if (m_responseText != "")
{
m_lastUpdate = time(NULL);

std::string escapedString = m_responseText;
CharacterDatabase.escape_string(escapedString);
CharacterDatabase.PExecute("UPDATE `character_ticket` SET `response_text` = '%s' "
"WHERE `guid` = '%u' and `ticket_id` = %u",
escapedString.c_str(), m_guid.GetCounter(), m_ticketId);
}
}

void GMTicket::CloseWithSurvey() const
Expand Down Expand Up @@ -189,10 +194,12 @@ void GMTicketMgr::Create(ObjectGuid guid, const char* text)
"(%u, '%s')",
guid.GetCounter(), escapedText.c_str());

//Get the id of the ticket, needed for logging whispers
// Get the id of the ticket, needed for logging whispers
// Limiting to the the most recent ticket of the player and avoid potential multiple returns
// if there is inconsistent data in table (e.g : more than 1 ticket unsolved for the same player (should never happen but..who knows..)
QueryResult* result = CharacterDatabase.PQuery("SELECT `ticket_id`, `guid`, `resolved` "
"FROM `character_ticket` "
"WHERE `guid` = %u AND `resolved` = 0;",
"WHERE `guid` = %u AND `resolved` = 0 ORDER BY `ticket_id` DESC LIMIT 1;",
guid.GetCounter());

CharacterDatabase.CommitTransaction();
Expand Down
7 changes: 7 additions & 0 deletions src/game/Object/ObjectMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5697,6 +5697,13 @@ void ObjectMgr::SetHighestGuids()
delete result;
}

result = CharacterDatabase.Query("SELECT MAX(`id`) FROM `item_text`");
if (result)
{
m_ItemTextGuids.Set((*result)[0].GetUInt32() + 1);
delete result;
}

// Cleanup other tables from nonexistent guids (>=m_hiItemGuid)
CharacterDatabase.BeginTransaction();
CharacterDatabase.PExecute("DELETE FROM `character_inventory` WHERE `item` >= '%u'", m_ItemGuids.GetNextAfterMaxUsed());
Expand Down
3 changes: 2 additions & 1 deletion src/game/Object/ObjectMgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ class ObjectMgr
}
uint32 GenerateItemTextID()
{
return m_ItemGuids.Generate();
return m_ItemTextGuids.Generate();
}
uint32 GenerateMailID()
{
Expand Down Expand Up @@ -1232,6 +1232,7 @@ class ObjectMgr
// first free low guid for selected guid type
ObjectGuidGenerator<HIGHGUID_PLAYER> m_CharGuids;
ObjectGuidGenerator<HIGHGUID_ITEM> m_ItemGuids;
ObjectGuidGenerator<HIGHGUID_ITEM> m_ItemTextGuids;
ObjectGuidGenerator<HIGHGUID_CORPSE> m_CorpseGuids;

QuestMap mQuestTemplates;
Expand Down
5 changes: 4 additions & 1 deletion src/game/Tools/Language.h
Original file line number Diff line number Diff line change
Expand Up @@ -1034,7 +1034,10 @@ enum MangosStrings
LANG_COMMAND_TICKET_OFFLINE_INFO = 1516,
LANG_COMMAND_TICKET_COUNT_ALL = 1517,
LANG_COMMAND_TICKET_ACCEPT_STATE = 1518,
// Room for more Level 2 1519-1599 not used
LANG_COMMAND_TICKET_RESPOND_MAIL_SUBJECT = 1519,
LANG_COMMAND_TICKET_RESPOND_MAIL_SIGNATURE = 1520,
LANG_COMMAND_TICKETCLOSED_PLAYER_NOTIF = 1521,
// Room for more Level 2 1522-1599 not used

// Outdoor PvP
LANG_OPVP_EP_CAPTURE_NPT_H = 1600,
Expand Down
1 change: 1 addition & 0 deletions src/game/Tools/PlayerDump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,7 @@ DumpReturn PlayerDumpReader::LoadDump(const std::string& file, uint32 account, s

// FIXME: current code with post-updating guids not safe for future per-map threads
sObjectMgr.m_ItemGuids.Set(sObjectMgr.m_ItemGuids.GetNextAfterMaxUsed() + items.size());
sObjectMgr.m_ItemTextGuids.Set(sObjectMgr.m_ItemTextGuids.GetNextAfterMaxUsed() + itemTexts.size());
sObjectMgr.m_MailIds.Set(sObjectMgr.m_MailIds.GetNextAfterMaxUsed() + mails.size());
sObjectMgr.m_ItemTextIds.Set(sObjectMgr.m_ItemTextIds.GetNextAfterMaxUsed() + itemTexts.size());

Expand Down
39 changes: 39 additions & 0 deletions src/game/WorldHandlers/Chat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,45 @@ void ChatHandler::PSendSysMessage(int32 entry, ...)
SendSysMessage(str);
}

void ChatHandler::PSendSysMessageMultiline(int32 entry, ...)
{
uint32 linecount = 0;

const char* format = GetMangosString(entry);
va_list ap;
char str[2048];
va_start(ap, entry);
vsnprintf(str, 2048, format, ap);
va_end(ap);

std::string mangosString(str);

/* Used for tracking our position within the string while iterating through it */
std::string::size_type pos = 0, nextpos;

/* Find the next occurance of @ in the string
* This is how newlines are represented */
while ((nextpos = mangosString.find("@@", pos)) != std::string::npos)
{
/* If these are not equal, it means a '@@' was found
* These are used to represent newlines in the string
* It is set by the code above here */
if (nextpos != pos)
{
/* Send the player a system message containing the substring from pos to nextpos - pos */
PSendSysMessage("%s", mangosString.substr(pos, nextpos - pos).c_str());
++linecount;
}
pos = nextpos + 2; // +2 because there are two @ as delimiter
}

/* There are no more newlines in our mangosString, so we send whatever is left */
if (pos < mangosString.length())
{
PSendSysMessage("%s", mangosString.substr(pos).c_str());
}
}

void ChatHandler::PSendSysMessage(const char* format, ...)
{
va_list ap;
Expand Down
1 change: 1 addition & 0 deletions src/game/WorldHandlers/Chat.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class ChatHandler
void SendSysMessage(int32 entry);
void PSendSysMessage(const char* format, ...) ATTR_PRINTF(2, 3);
void PSendSysMessage(int32 entry, ...);
void PSendSysMessageMultiline(int32 entry, ...);

bool ParseCommands(const char* text);
ChatCommand const* FindCommand(char const* text);
Expand Down
5 changes: 3 additions & 2 deletions src/game/WorldHandlers/GMTicketHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ void WorldSession::SendGMTicketGetTicket(uint32 status, GMTicket* ticket /*= NUL
{
std::string text = ticket ? ticket->GetText() : "";

if (ticket && ticket->HasResponse())
// Removed : Was adding ticket response to ticket text !
/*if (ticket && ticket->HasResponse())
{
text += "\n\n";
Expand All @@ -44,7 +45,7 @@ void WorldSession::SendGMTicketGetTicket(uint32 status, GMTicket* ticket /*= NUL
snprintf(textBuf, 1024, textFormat.c_str(), ticket->GetResponse());
text += textBuf;
}
}*/

int len = text.size() + 1;
WorldPacket data(SMSG_GMTICKET_GETTICKET, (4 + len + 1 + 4 + 2 + 4 + 4));
Expand Down
6 changes: 3 additions & 3 deletions src/shared/revision.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
#define CHAR_DB_UPDATE_DESCRIPTION "Add_Field_Comments"

#define WORLD_DB_VERSION_NR 21
#define WORLD_DB_STRUCTURE_NR 16
#define WORLD_DB_CONTENT_NR 016
#define WORLD_DB_UPDATE_DESCRIPTION "Fix typo in quest 5064"
#define WORLD_DB_STRUCTURE_NR 17
#define WORLD_DB_CONTENT_NR 053
#define WORLD_DB_UPDATE_DESCRIPTION "GM_tickets_handling_fixes_pt1"
#endif // __REVISION_H__

0 comments on commit 778941c

Please sign in to comment.