Gm ticket handling fixes (#90)
* 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
This commit is contained in:
parent
7a749e0561
commit
778941c09a
@ -46,6 +46,7 @@
|
|||||||
#include "GMTicketMgr.h"
|
#include "GMTicketMgr.h"
|
||||||
#include "WaypointManager.h"
|
#include "WaypointManager.h"
|
||||||
#include "DBCStores.h"
|
#include "DBCStores.h"
|
||||||
|
#include "Mail.h"
|
||||||
#include "Util.h"
|
#include "Util.h"
|
||||||
#include "GridNotifiers.h"
|
#include "GridNotifiers.h"
|
||||||
#include "GridNotifiersImpl.h"
|
#include "GridNotifiersImpl.h"
|
||||||
@ -2852,7 +2853,16 @@ 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))
|
if (!pPlayer && !sWorld.getConfig(CONFIG_BOOL_GM_TICKET_OFFLINE_CLOSING))
|
||||||
{
|
{
|
||||||
@ -2860,12 +2870,29 @@ bool ChatHandler::HandleTicketCloseCommand(char* args)
|
|||||||
return false;
|
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();
|
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?
|
//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
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@ -3085,18 +3112,65 @@ bool ChatHandler::HandleTicketRespondCommand(char* args)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the response text to the ticket
|
||||||
ticket->SetResponseText(args);
|
ticket->SetResponseText(args);
|
||||||
|
|
||||||
if (Player* pl = sObjectMgr.GetPlayer(ticket->GetPlayerGuid()))
|
// Send in-game email with ticket answer
|
||||||
{
|
MailDraft draft;
|
||||||
pl->GetSession()->SendGMTicketGetTicket(0x06, ticket);
|
|
||||||
//How should we error here?
|
const char * signatureFormat = GetMangosString(LANG_COMMAND_TICKET_RESPOND_MAIL_SIGNATURE);
|
||||||
if (m_session)
|
const uint32 signatureBufferSize = 256;
|
||||||
{
|
char signature[signatureBufferSize];
|
||||||
m_session->GetPlayer()->Whisper(args, LANG_UNIVERSAL, pl->GetObjectGuid());
|
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)
|
||||||
|
{
|
||||||
|
if (player->GetSession()->GetSecurity() >= SEC_GAMEMASTER && player->isAcceptTickets())
|
||||||
|
{
|
||||||
|
ChatHandler(player).PSendSysMessage(LANG_COMMAND_TICKETCLOSED_NAME, ticketId, target_name.c_str(), m_session->GetPlayer()->GetName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,20 +84,25 @@ void GMTicket::SetText(const char* text)
|
|||||||
std::string escapedString = m_text;
|
std::string escapedString = m_text;
|
||||||
CharacterDatabase.escape_string(escapedString);
|
CharacterDatabase.escape_string(escapedString);
|
||||||
CharacterDatabase.PExecute("UPDATE `character_ticket` SET `ticket_text` = '%s' "
|
CharacterDatabase.PExecute("UPDATE `character_ticket` SET `ticket_text` = '%s' "
|
||||||
"WHERE `guid` = '%u'",
|
"WHERE `guid` = '%u' AND `ticket_id` = %u",
|
||||||
escapedString.c_str(), m_guid.GetCounter());
|
escapedString.c_str(), m_guid.GetCounter(), m_ticketId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMTicket::SetResponseText(const char* text)
|
void GMTicket::SetResponseText(const char* text)
|
||||||
{
|
{
|
||||||
m_responseText = text ? text : "";
|
m_responseText = text ? text : "";
|
||||||
m_lastUpdate = time(NULL);
|
|
||||||
|
// Perform action in DB only if text is not empty
|
||||||
|
if (m_responseText != "")
|
||||||
|
{
|
||||||
|
m_lastUpdate = time(NULL);
|
||||||
|
|
||||||
std::string escapedString = m_responseText;
|
std::string escapedString = m_responseText;
|
||||||
CharacterDatabase.escape_string(escapedString);
|
CharacterDatabase.escape_string(escapedString);
|
||||||
CharacterDatabase.PExecute("UPDATE `character_ticket` SET `response_text` = '%s' "
|
CharacterDatabase.PExecute("UPDATE `character_ticket` SET `response_text` = '%s' "
|
||||||
"WHERE `guid` = '%u'",
|
"WHERE `guid` = '%u' and `ticket_id` = %u",
|
||||||
escapedString.c_str(), m_guid.GetCounter());
|
escapedString.c_str(), m_guid.GetCounter(), m_ticketId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMTicket::CloseWithSurvey() const
|
void GMTicket::CloseWithSurvey() const
|
||||||
@ -189,10 +194,12 @@ void GMTicketMgr::Create(ObjectGuid guid, const char* text)
|
|||||||
"(%u, '%s')",
|
"(%u, '%s')",
|
||||||
guid.GetCounter(), escapedText.c_str());
|
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` "
|
QueryResult* result = CharacterDatabase.PQuery("SELECT `ticket_id`, `guid`, `resolved` "
|
||||||
"FROM `character_ticket` "
|
"FROM `character_ticket` "
|
||||||
"WHERE `guid` = %u AND `resolved` = 0;",
|
"WHERE `guid` = %u AND `resolved` = 0 ORDER BY `ticket_id` DESC LIMIT 1;",
|
||||||
guid.GetCounter());
|
guid.GetCounter());
|
||||||
|
|
||||||
CharacterDatabase.CommitTransaction();
|
CharacterDatabase.CommitTransaction();
|
||||||
|
@ -5697,6 +5697,13 @@ void ObjectMgr::SetHighestGuids()
|
|||||||
delete result;
|
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)
|
// Cleanup other tables from nonexistent guids (>=m_hiItemGuid)
|
||||||
CharacterDatabase.BeginTransaction();
|
CharacterDatabase.BeginTransaction();
|
||||||
CharacterDatabase.PExecute("DELETE FROM `character_inventory` WHERE `item` >= '%u'", m_ItemGuids.GetNextAfterMaxUsed());
|
CharacterDatabase.PExecute("DELETE FROM `character_inventory` WHERE `item` >= '%u'", m_ItemGuids.GetNextAfterMaxUsed());
|
||||||
|
@ -826,7 +826,7 @@ class ObjectMgr
|
|||||||
}
|
}
|
||||||
uint32 GenerateItemTextID()
|
uint32 GenerateItemTextID()
|
||||||
{
|
{
|
||||||
return m_ItemGuids.Generate();
|
return m_ItemTextGuids.Generate();
|
||||||
}
|
}
|
||||||
uint32 GenerateMailID()
|
uint32 GenerateMailID()
|
||||||
{
|
{
|
||||||
@ -1232,6 +1232,7 @@ class ObjectMgr
|
|||||||
// first free low guid for selected guid type
|
// first free low guid for selected guid type
|
||||||
ObjectGuidGenerator<HIGHGUID_PLAYER> m_CharGuids;
|
ObjectGuidGenerator<HIGHGUID_PLAYER> m_CharGuids;
|
||||||
ObjectGuidGenerator<HIGHGUID_ITEM> m_ItemGuids;
|
ObjectGuidGenerator<HIGHGUID_ITEM> m_ItemGuids;
|
||||||
|
ObjectGuidGenerator<HIGHGUID_ITEM> m_ItemTextGuids;
|
||||||
ObjectGuidGenerator<HIGHGUID_CORPSE> m_CorpseGuids;
|
ObjectGuidGenerator<HIGHGUID_CORPSE> m_CorpseGuids;
|
||||||
|
|
||||||
QuestMap mQuestTemplates;
|
QuestMap mQuestTemplates;
|
||||||
|
@ -1034,7 +1034,10 @@ enum MangosStrings
|
|||||||
LANG_COMMAND_TICKET_OFFLINE_INFO = 1516,
|
LANG_COMMAND_TICKET_OFFLINE_INFO = 1516,
|
||||||
LANG_COMMAND_TICKET_COUNT_ALL = 1517,
|
LANG_COMMAND_TICKET_COUNT_ALL = 1517,
|
||||||
LANG_COMMAND_TICKET_ACCEPT_STATE = 1518,
|
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
|
// Outdoor PvP
|
||||||
LANG_OPVP_EP_CAPTURE_NPT_H = 1600,
|
LANG_OPVP_EP_CAPTURE_NPT_H = 1600,
|
||||||
|
@ -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
|
// 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_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_MailIds.Set(sObjectMgr.m_MailIds.GetNextAfterMaxUsed() + mails.size());
|
||||||
sObjectMgr.m_ItemTextIds.Set(sObjectMgr.m_ItemTextIds.GetNextAfterMaxUsed() + itemTexts.size());
|
sObjectMgr.m_ItemTextIds.Set(sObjectMgr.m_ItemTextIds.GetNextAfterMaxUsed() + itemTexts.size());
|
||||||
|
|
||||||
|
@ -1006,6 +1006,45 @@ void ChatHandler::PSendSysMessage(int32 entry, ...)
|
|||||||
SendSysMessage(str);
|
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, ...)
|
void ChatHandler::PSendSysMessage(const char* format, ...)
|
||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
@ -93,6 +93,7 @@ class ChatHandler
|
|||||||
void SendSysMessage(int32 entry);
|
void SendSysMessage(int32 entry);
|
||||||
void PSendSysMessage(const char* format, ...) ATTR_PRINTF(2, 3);
|
void PSendSysMessage(const char* format, ...) ATTR_PRINTF(2, 3);
|
||||||
void PSendSysMessage(int32 entry, ...);
|
void PSendSysMessage(int32 entry, ...);
|
||||||
|
void PSendSysMessageMultiline(int32 entry, ...);
|
||||||
|
|
||||||
bool ParseCommands(const char* text);
|
bool ParseCommands(const char* text);
|
||||||
ChatCommand const* FindCommand(char const* text);
|
ChatCommand const* FindCommand(char const* text);
|
||||||
|
@ -35,7 +35,8 @@ void WorldSession::SendGMTicketGetTicket(uint32 status, GMTicket* ticket /*= NUL
|
|||||||
{
|
{
|
||||||
std::string text = ticket ? ticket->GetText() : "";
|
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";
|
text += "\n\n";
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ void WorldSession::SendGMTicketGetTicket(uint32 status, GMTicket* ticket /*= NUL
|
|||||||
snprintf(textBuf, 1024, textFormat.c_str(), ticket->GetResponse());
|
snprintf(textBuf, 1024, textFormat.c_str(), ticket->GetResponse());
|
||||||
|
|
||||||
text += textBuf;
|
text += textBuf;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
int len = text.size() + 1;
|
int len = text.size() + 1;
|
||||||
WorldPacket data(SMSG_GMTICKET_GETTICKET, (4 + len + 1 + 4 + 2 + 4 + 4));
|
WorldPacket data(SMSG_GMTICKET_GETTICKET, (4 + len + 1 + 4 + 2 + 4 + 4));
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
#define CHAR_DB_UPDATE_DESCRIPTION "Add_Field_Comments"
|
#define CHAR_DB_UPDATE_DESCRIPTION "Add_Field_Comments"
|
||||||
|
|
||||||
#define WORLD_DB_VERSION_NR 21
|
#define WORLD_DB_VERSION_NR 21
|
||||||
#define WORLD_DB_STRUCTURE_NR 16
|
#define WORLD_DB_STRUCTURE_NR 17
|
||||||
#define WORLD_DB_CONTENT_NR 016
|
#define WORLD_DB_CONTENT_NR 053
|
||||||
#define WORLD_DB_UPDATE_DESCRIPTION "Fix typo in quest 5064"
|
#define WORLD_DB_UPDATE_DESCRIPTION "GM_tickets_handling_fixes_pt1"
|
||||||
#endif // __REVISION_H__
|
#endif // __REVISION_H__
|
||||||
|
Loading…
x
Reference in New Issue
Block a user