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 "WaypointManager.h"
|
||||
#include "DBCStores.h"
|
||||
#include "Mail.h"
|
||||
#include "Util.h"
|
||||
#include "GridNotifiers.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))
|
||||
{
|
||||
@ -2860,12 +2870,29 @@ bool ChatHandler::HandleTicketCloseCommand(char* args)
|
||||
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;
|
||||
}
|
||||
@ -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())
|
||||
{
|
||||
pl->GetSession()->SendGMTicketGetTicket(0x06, ticket);
|
||||
//How should we error here?
|
||||
if (m_session)
|
||||
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)
|
||||
{
|
||||
m_session->GetPlayer()->Whisper(args, LANG_UNIVERSAL, pl->GetObjectGuid());
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -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 : "";
|
||||
|
||||
// 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'",
|
||||
escapedString.c_str(), m_guid.GetCounter());
|
||||
"WHERE `guid` = '%u' and `ticket_id` = %u",
|
||||
escapedString.c_str(), m_guid.GetCounter(), m_ticketId);
|
||||
}
|
||||
}
|
||||
|
||||
void GMTicket::CloseWithSurvey() const
|
||||
@ -190,9 +195,11 @@ void GMTicketMgr::Create(ObjectGuid guid, const char* text)
|
||||
guid.GetCounter(), escapedText.c_str());
|
||||
|
||||
// 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();
|
||||
|
@ -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());
|
||||
|
@ -826,7 +826,7 @@ class ObjectMgr
|
||||
}
|
||||
uint32 GenerateItemTextID()
|
||||
{
|
||||
return m_ItemGuids.Generate();
|
||||
return m_ItemTextGuids.Generate();
|
||||
}
|
||||
uint32 GenerateMailID()
|
||||
{
|
||||
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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";
|
||||
|
||||
@ -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));
|
||||
|
@ -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__
|
||||
|
Loading…
x
Reference in New Issue
Block a user