diff --git a/src/game/ChatCommands/Level2.cpp b/src/game/ChatCommands/Level2.cpp index 81753f72..00863a92 100644 --- a/src/game/ChatCommands/Level2.cpp +++ b/src/game/ChatCommands/Level2.cpp @@ -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 %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,18 +3112,65 @@ bool ChatHandler::HandleTicketRespondCommand(char* args) return false; } + // Set the response text to the ticket ticket->SetResponseText(args); - if (Player* pl = sObjectMgr.GetPlayer(ticket->GetPlayerGuid())) - { - pl->GetSession()->SendGMTicketGetTicket(0x06, ticket); - //How should we error here? - if (m_session) - { - m_session->GetPlayer()->Whisper(args, LANG_UNIVERSAL, pl->GetObjectGuid()); - } + // 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) + { + 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; } diff --git a/src/game/Object/GMTicketMgr.cpp b/src/game/Object/GMTicketMgr.cpp index f03b138d..a6a796ba 100644 --- a/src/game/Object/GMTicketMgr.cpp +++ b/src/game/Object/GMTicketMgr.cpp @@ -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); + + // 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()); + 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 @@ -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(); diff --git a/src/game/Object/ObjectMgr.cpp b/src/game/Object/ObjectMgr.cpp index 93ad8660..1c49c455 100644 --- a/src/game/Object/ObjectMgr.cpp +++ b/src/game/Object/ObjectMgr.cpp @@ -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()); diff --git a/src/game/Object/ObjectMgr.h b/src/game/Object/ObjectMgr.h index d6215bd2..33159ac5 100644 --- a/src/game/Object/ObjectMgr.h +++ b/src/game/Object/ObjectMgr.h @@ -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 m_CharGuids; ObjectGuidGenerator m_ItemGuids; + ObjectGuidGenerator m_ItemTextGuids; ObjectGuidGenerator m_CorpseGuids; QuestMap mQuestTemplates; diff --git a/src/game/Tools/Language.h b/src/game/Tools/Language.h index 9e2cc1e2..a202d08b 100644 --- a/src/game/Tools/Language.h +++ b/src/game/Tools/Language.h @@ -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, diff --git a/src/game/Tools/PlayerDump.cpp b/src/game/Tools/PlayerDump.cpp index 7346bbc0..d50baa52 100644 --- a/src/game/Tools/PlayerDump.cpp +++ b/src/game/Tools/PlayerDump.cpp @@ -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()); diff --git a/src/game/WorldHandlers/Chat.cpp b/src/game/WorldHandlers/Chat.cpp index b7bed635..b9b52dc9 100644 --- a/src/game/WorldHandlers/Chat.cpp +++ b/src/game/WorldHandlers/Chat.cpp @@ -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; diff --git a/src/game/WorldHandlers/Chat.h b/src/game/WorldHandlers/Chat.h index b0bac5a0..e0f83611 100644 --- a/src/game/WorldHandlers/Chat.h +++ b/src/game/WorldHandlers/Chat.h @@ -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); diff --git a/src/game/WorldHandlers/GMTicketHandler.cpp b/src/game/WorldHandlers/GMTicketHandler.cpp index e4bab648..8ab0f6ab 100644 --- a/src/game/WorldHandlers/GMTicketHandler.cpp +++ b/src/game/WorldHandlers/GMTicketHandler.cpp @@ -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)); diff --git a/src/shared/revision.h b/src/shared/revision.h index a2a67594..0b39a267 100644 --- a/src/shared/revision.h +++ b/src/shared/revision.h @@ -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__