/* * Copyright (C) 2008-2014 TrinityCore * Copyright (C) 2005-2009 MaNGOS * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "Common.h" #include "TicketMgr.h" #include "DatabaseEnv.h" #include "Log.h" #include "Language.h" #include "WorldPacket.h" #include "WorldSession.h" #include "Chat.h" #include "World.h" #include "Player.h" #include "Opcodes.h" inline float GetAge(uint64 t) { return float(time(NULL) - t) / DAY; } /////////////////////////////////////////////////////////////////////////////////////////////////// // GM ticket GmTicket::GmTicket() : _id(0), _posX(0), _posY(0), _posZ(0), _mapId(0), _createTime(0), _lastModifiedTime(0), _completed(false), _escalatedStatus(TICKET_UNASSIGNED), _viewed(false), _needResponse(false), _needMoreHelp(false) { } GmTicket::GmTicket(Player* player) : _posX(0), _posY(0), _posZ(0), _mapId(0), _createTime(time(NULL)), _lastModifiedTime(time(NULL)), _completed(false), _escalatedStatus(TICKET_UNASSIGNED), _viewed(false), _needResponse(false), _needMoreHelp(false) { _id = sTicketMgr->GenerateTicketId(); _playerName = player->GetName(); _playerGuid = player->GetGUID(); } GmTicket::~GmTicket() { } bool GmTicket::LoadFromDB(Field* fields) { // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // ticketId, guid, name, message, createTime, mapId, posX, posY, posZ, lastModifiedTime, closedBy, assignedTo, comment, response, completed, escalated, viewed, haveTicket uint8 index = 0; _id = fields[ index].GetUInt32(); _playerGuid = ObjectGuid(HIGHGUID_PLAYER, fields[++index].GetUInt32()); _playerName = fields[++index].GetString(); _message = fields[++index].GetString(); _createTime = fields[++index].GetUInt32(); _mapId = fields[++index].GetUInt16(); _posX = fields[++index].GetFloat(); _posY = fields[++index].GetFloat(); _posZ = fields[++index].GetFloat(); _lastModifiedTime = fields[++index].GetUInt32(); _closedBy = ObjectGuid(uint64(fields[++index].GetInt32())); _assignedTo = ObjectGuid(HIGHGUID_PLAYER, fields[++index].GetUInt32()); _comment = fields[++index].GetString(); _response = fields[++index].GetString(); _completed = fields[++index].GetBool(); _escalatedStatus = GMTicketEscalationStatus(fields[++index].GetUInt8()); _viewed = fields[++index].GetBool(); _needMoreHelp = fields[++index].GetBool(); return true; } void GmTicket::SaveToDB(SQLTransaction& trans) const { // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // ticketId, guid, name, message, createTime, mapId, posX, posY, posZ, lastModifiedTime, closedBy, assignedTo, comment, completed, escalated, viewed uint8 index = 0; PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_GM_TICKET); stmt->setUInt32( index, _id); stmt->setUInt32(++index, _playerGuid.GetCounter()); stmt->setString(++index, _playerName); stmt->setString(++index, _message); stmt->setUInt32(++index, uint32(_createTime)); stmt->setUInt16(++index, _mapId); stmt->setFloat (++index, _posX); stmt->setFloat (++index, _posY); stmt->setFloat (++index, _posZ); stmt->setUInt32(++index, uint32(_lastModifiedTime)); stmt->setInt32 (++index, int32(_closedBy.GetCounter())); stmt->setUInt32(++index, _assignedTo.GetCounter()); stmt->setString(++index, _comment); stmt->setString(++index, _response); stmt->setBool (++index, _completed); stmt->setUInt8 (++index, uint8(_escalatedStatus)); stmt->setBool (++index, _viewed); stmt->setBool (++index, _needMoreHelp); CharacterDatabase.ExecuteOrAppend(trans, stmt); } void GmTicket::DeleteFromDB() { PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GM_TICKET); stmt->setUInt32(0, _id); CharacterDatabase.Execute(stmt); } void GmTicket::WritePacket(WorldPacket& data) const { data << uint32(GMTICKET_STATUS_HASTEXT); data << uint32(_id); data << _message; data << uint8(_needMoreHelp); data << GetAge(_lastModifiedTime); if (GmTicket* ticket = sTicketMgr->GetOldestOpenTicket()) data << GetAge(ticket->GetLastModifiedTime()); else data << float(0); // I am not sure how blizzlike this is, and we don't really have a way to find out data << GetAge(sTicketMgr->GetLastChange()); data << uint8(std::min(_escalatedStatus, TICKET_IN_ESCALATION_QUEUE)); // escalated data data << uint8(_viewed ? GMTICKET_OPENEDBYGM_STATUS_OPENED : GMTICKET_OPENEDBYGM_STATUS_NOT_OPENED); // whether or not it has been viewed // TODO: implement these std::string waitTimeOverrideMessage = ""; data << waitTimeOverrideMessage; data << uint32(0); // waitTimeOverrideMinutes } void GmTicket::SendResponse(WorldSession* session) const { WorldPacket data(SMSG_GMRESPONSE_RECEIVED); data << uint32(1); // responseID data << uint32(_id); // ticketID data << _message.c_str(); size_t len = _response.size(); char const* s = _response.c_str(); for (int i = 0; i < 4; i++) { if (len) { size_t writeLen = std::min(len, 3999); data.append(s, writeLen); len -= writeLen; s += writeLen; } data << uint8(0); } session->SendPacket(&data); } std::string GmTicket::FormatMessageString(ChatHandler& handler, bool detailed) const { time_t curTime = time(NULL); std::stringstream ss; ss << handler.PGetParseString(LANG_COMMAND_TICKETLISTGUID, _id); ss << handler.PGetParseString(LANG_COMMAND_TICKETLISTNAME, _playerName.c_str()); ss << handler.PGetParseString(LANG_COMMAND_TICKETLISTAGECREATE, (secsToTimeString(curTime - _createTime, true, false)).c_str()); ss << handler.PGetParseString(LANG_COMMAND_TICKETLISTAGE, (secsToTimeString(curTime - _lastModifiedTime, true, false)).c_str()); std::string name; if (sObjectMgr->GetPlayerNameByGUID(_assignedTo, name)) ss << handler.PGetParseString(LANG_COMMAND_TICKETLISTASSIGNEDTO, name.c_str()); if (detailed) { ss << handler.PGetParseString(LANG_COMMAND_TICKETLISTMESSAGE, _message.c_str()); if (!_comment.empty()) ss << handler.PGetParseString(LANG_COMMAND_TICKETLISTCOMMENT, _comment.c_str()); if (!_response.empty()) ss << handler.PGetParseString(LANG_COMMAND_TICKETLISTRESPONSE, _response.c_str()); } return ss.str(); } std::string GmTicket::FormatMessageString(ChatHandler& handler, const char* szClosedName, const char* szAssignedToName, const char* szUnassignedName, const char* szDeletedName, const char* szCompletedName) const { std::stringstream ss; ss << handler.PGetParseString(LANG_COMMAND_TICKETLISTGUID, _id); ss << handler.PGetParseString(LANG_COMMAND_TICKETLISTNAME, _playerName.c_str()); if (szClosedName) ss << handler.PGetParseString(LANG_COMMAND_TICKETCLOSED, szClosedName); if (szAssignedToName) ss << handler.PGetParseString(LANG_COMMAND_TICKETLISTASSIGNEDTO, szAssignedToName); if (szUnassignedName) ss << handler.PGetParseString(LANG_COMMAND_TICKETLISTUNASSIGNED, szUnassignedName); if (szDeletedName) ss << handler.PGetParseString(LANG_COMMAND_TICKETDELETED, szDeletedName); if (szCompletedName) ss << handler.PGetParseString(LANG_COMMAND_TICKETCOMPLETED, szCompletedName); return ss.str(); } void GmTicket::SetUnassigned() { _assignedTo.Clear(); switch (_escalatedStatus) { case TICKET_ASSIGNED: _escalatedStatus = TICKET_UNASSIGNED; break; case TICKET_ESCALATED_ASSIGNED: _escalatedStatus = TICKET_IN_ESCALATION_QUEUE; break; case TICKET_UNASSIGNED: case TICKET_IN_ESCALATION_QUEUE: default: break; } } void GmTicket::SetPosition(uint32 mapId, float x, float y, float z) { _mapId = mapId; _posX = x; _posY = y; _posZ = z; } void GmTicket::SetGmAction(uint32 needResponse, bool needMoreHelp) { _needResponse = (needResponse == 17); // Requires GM response. 17 = true, 1 = false (17 is default) _needMoreHelp = needMoreHelp; // Requests further GM interaction on a ticket to which a GM has already responded. Basically means "has a new ticket" } void GmTicket::TeleportTo(Player* player) const { player->TeleportTo(_mapId, _posX, _posY, _posZ, 0.0f, 0); } void GmTicket::SetChatLog(std::list time, std::string const& log) { std::stringstream ss(log); std::stringstream newss; std::string line; while (std::getline(ss, line) && !time.empty()) { newss << secsToTimeString(time.front()) << ": " << line << "\n"; time.pop_front(); } _chatLog = newss.str(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // Ticket manager TicketMgr::TicketMgr() : _status(true), _lastTicketId(0), _lastSurveyId(0), _openTicketCount(0), _lastChange(time(NULL)) { } TicketMgr::~TicketMgr() { for (GmTicketList::const_iterator itr = _ticketList.begin(); itr != _ticketList.end(); ++itr) delete itr->second; } void TicketMgr::Initialize() { SetStatus(sWorld->getBoolConfig(CONFIG_ALLOW_TICKETS)); } void TicketMgr::ResetTickets() { for (GmTicketList::const_iterator itr = _ticketList.begin(); itr != _ticketList.end();) { if (itr->second->IsClosed()) { uint32 ticketId = itr->second->GetId(); ++itr; sTicketMgr->RemoveTicket(ticketId); } else ++itr; } _lastTicketId = 0; PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ALL_GM_TICKETS); CharacterDatabase.Execute(stmt); } void TicketMgr::LoadTickets() { uint32 oldMSTime = getMSTime(); for (GmTicketList::const_iterator itr = _ticketList.begin(); itr != _ticketList.end(); ++itr) delete itr->second; _ticketList.clear(); _lastTicketId = 0; _openTicketCount = 0; PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_GM_TICKETS); PreparedQueryResult result = CharacterDatabase.Query(stmt); if (!result) { TC_LOG_INFO("server.loading", ">> Loaded 0 GM tickets. DB table `gm_tickets` is empty!"); return; } uint32 count = 0; do { Field* fields = result->Fetch(); GmTicket* ticket = new GmTicket(); if (!ticket->LoadFromDB(fields)) { delete ticket; continue; } if (!ticket->IsClosed()) ++_openTicketCount; // Update max ticket id if necessary uint32 id = ticket->GetId(); if (_lastTicketId < id) _lastTicketId = id; _ticketList[id] = ticket; ++count; } while (result->NextRow()); TC_LOG_INFO("server.loading", ">> Loaded %u GM tickets in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } void TicketMgr::LoadSurveys() { // we don't actually load anything into memory here as there's no reason to _lastSurveyId = 0; uint32 oldMSTime = getMSTime(); if (QueryResult result = CharacterDatabase.Query("SELECT MAX(surveyId) FROM gm_surveys")) _lastSurveyId = (*result)[0].GetUInt32(); TC_LOG_INFO("server.loading", ">> Loaded GM Survey count from database in %u ms", GetMSTimeDiffToNow(oldMSTime)); } void TicketMgr::AddTicket(GmTicket* ticket) { _ticketList[ticket->GetId()] = ticket; if (!ticket->IsClosed()) ++_openTicketCount; SQLTransaction trans = SQLTransaction(NULL); ticket->SaveToDB(trans); } void TicketMgr::CloseTicket(uint32 ticketId, ObjectGuid source) { if (GmTicket* ticket = GetTicket(ticketId)) { SQLTransaction trans = SQLTransaction(NULL); ticket->SetClosedBy(source); if (source) --_openTicketCount; ticket->SaveToDB(trans); } } void TicketMgr::RemoveTicket(uint32 ticketId) { if (GmTicket* ticket = GetTicket(ticketId)) { ticket->DeleteFromDB(); _ticketList.erase(ticketId); delete ticket; } } void TicketMgr::ShowList(ChatHandler& handler, bool onlineOnly) const { handler.SendSysMessage(onlineOnly ? LANG_COMMAND_TICKETSHOWONLINELIST : LANG_COMMAND_TICKETSHOWLIST); for (GmTicketList::const_iterator itr = _ticketList.begin(); itr != _ticketList.end(); ++itr) if (!itr->second->IsClosed() && !itr->second->IsCompleted()) if (!onlineOnly || itr->second->GetPlayer()) handler.SendSysMessage(itr->second->FormatMessageString(handler).c_str()); } void TicketMgr::ShowClosedList(ChatHandler& handler) const { handler.SendSysMessage(LANG_COMMAND_TICKETSHOWCLOSEDLIST); for (GmTicketList::const_iterator itr = _ticketList.begin(); itr != _ticketList.end(); ++itr) if (itr->second->IsClosed()) handler.SendSysMessage(itr->second->FormatMessageString(handler).c_str()); } void TicketMgr::ShowEscalatedList(ChatHandler& handler) const { handler.SendSysMessage(LANG_COMMAND_TICKETSHOWESCALATEDLIST); for (GmTicketList::const_iterator itr = _ticketList.begin(); itr != _ticketList.end(); ++itr) if (!itr->second->IsClosed() && itr->second->GetEscalatedStatus() == TICKET_IN_ESCALATION_QUEUE) handler.SendSysMessage(itr->second->FormatMessageString(handler).c_str()); } void TicketMgr::SendTicket(WorldSession* session, GmTicket* ticket) const { WorldPacket data(SMSG_GMTICKET_GETTICKET, (ticket ? (4 + 4 + 1 + 4 + 4 + 4 + 1 + 1) : 4)); if (ticket) ticket->WritePacket(data); else data << uint32(GMTICKET_STATUS_DEFAULT); session->SendPacket(&data); }