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:
Elmsroth 2020-05-11 10:16:01 +02:00 committed by GitHub
parent 7a749e0561
commit 778941c09a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 161 additions and 27 deletions

View File

@ -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;
}

View File

@ -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();

View File

@ -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());

View File

@ -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;

View File

@ -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,

View File

@ -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());

View File

@ -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;

View File

@ -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);

View File

@ -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));

View File

@ -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__