/* * Copyright (C) 2008-2012 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 "WorldPacket.h" #include "WorldSession.h" #include "Chat.h" #include "World.h" inline float GetAge(uint64 t) { return float(time(NULL) - t) / DAY; } /////////////////////////////////////////////////////////////////////////////////////////////////// // GM ticket GmTicket::GmTicket() { } GmTicket::GmTicket(Player* player, WorldPacket& recv_data) : _createTime(time(NULL)), _lastModifiedTime(time(NULL)), _closedBy(0), _assignedTo(0), _completed(false), _escalatedStatus(TICKET_UNASSIGNED) { _id = sTicketMgr->GenerateTicketId(); _playerName = player->GetName(); _playerGuid = player->GetGUID(); uint32 mapId; recv_data >> mapId; _mapId = mapId; recv_data >> _posX; recv_data >> _posY; recv_data >> _posZ; recv_data >> _message; uint32 unk1; recv_data >> unk1; // not sure what this is... replyTo? uint8 needResponse; recv_data >> needResponse; // always 1/0 -- not sure what retail does with this } GmTicket::~GmTicket() { } bool GmTicket::LoadFromDB(Field* fields) { uint8 index = 0; _id = fields[ index].GetUInt32(); _playerGuid = MAKE_NEW_GUID(fields[++index].GetUInt32(), 0, HIGHGUID_PLAYER); _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 = fields[++index].GetInt32(); _assignedTo = MAKE_NEW_GUID(fields[++index].GetUInt32(), 0, HIGHGUID_PLAYER); _comment = fields[++index].GetString(); _completed = fields[++index].GetBool(); _escalatedStatus = GMTicketEscalationStatus(fields[++index].GetUInt8()); _viewed = fields[++index].GetBool(); return true; } void GmTicket::SaveToDB(SQLTransaction& trans) const { uint8 index = 0; PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_GM_TICKET); stmt->setUInt32( index, _id); stmt->setUInt32(++index, GUID_LOPART(_playerGuid)); 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, GUID_LOPART(_closedBy)); stmt->setUInt32(++index, GUID_LOPART(_assignedTo)); stmt->setString(++index, _comment); stmt->setBool (++index, _completed); stmt->setUInt8 (++index, uint8(_escalatedStatus)); stmt->setBool (++index, _viewed); 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 << 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 } void GmTicket::SendResponse(WorldSession* session) const { WorldPacket data(SMSG_GMRESPONSE_RECEIVED); data << uint32(1); // unk? Zor says "hasActiveTicket" data << uint32(0); // can-edit - always 1 or 0, not flags data << _message.c_str(); data << _response.c_str(); 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()); } return ss.str(); } std::string GmTicket::FormatMessageString(ChatHandler& handler, const char* szClosedName, const char* szAssignedToName, const char* szUnassignedName, const char* szDeletedName) 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); return ss.str(); } void GmTicket::SetUnassigned() { _assignedTo = 0; 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::TeleportTo(Player* player) const { player->TeleportTo(_mapId, _posX, _posY, _posZ, 1, 0); } /////////////////////////////////////////////////////////////////////////////////////////////////// // Ticket manager TicketMgr::TicketMgr() : _status(true), _lastTicketId(0), _lastSurveyId(0), _openTicketCount(0), _lastChange(time(NULL)) { } void TicketMgr::Initialize() { SetStatus(sWorld->getBoolConfig(CONFIG_ALLOW_TICKETS)); } void TicketMgr::ResetTickets() { for (GmTicketList::const_iterator itr = _ticketList.begin(); itr != _ticketList.end(); ++itr) if (itr->second->IsClosed()) sTicketMgr->RemoveTicket(itr->second->GetId()); _lastTicketId = 0; PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ALL_GM_TICKETS); CharacterDatabase.Execute(stmt); } void TicketMgr::LoadTickets() { uint32 oldMSTime = getMSTime(); if (!_ticketList.empty()) for (GmTicketList::const_iterator itr = _ticketList.begin(); itr != _ticketList.end(); ++itr) if (itr->second) delete itr->second; _ticketList.clear(); _lastTicketId = 0; _openTicketCount = 0; PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_GM_TICKETS); PreparedQueryResult result = CharacterDatabase.Query(stmt); if (!result) { sLog->outString(">> Loaded 0 GM tickets. DB table `gm_tickets` is empty!"); sLog->outString(); 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()); sLog->outString(">> Loaded %u GM tickets in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } 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(); sLog->outString(">> Loaded GM Survey count from database in %u ms", GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } 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, int64 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); } } 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 { uint32 status = GMTICKET_STATUS_DEFAULT; std::string message; if (ticket) { message = ticket->GetMessage(); status = GMTICKET_STATUS_HASTEXT; } WorldPacket data(SMSG_GMTICKET_GETTICKET, (4 + 4 + (ticket ? message.length() + 1 + 4 + 4 + 4 + 1 + 1 : 0))); data << uint32(status); // standard 0x0A, 0x06 if text present data << uint32(1); // g_HasActiveGMTicket -- not a flag if (ticket) { data << message.c_str(); // ticket text data << uint8(0x7); // ticket category; why is this hardcoded? does it make a diff re: client? // we've got the easy stuff done by now. // Now we need to go through the client logic for displaying various levels of ticket load if (ticket) ticket->WritePacket(data); else { // we can't actually get any numbers here... data << float(0); data << float(0); data << float(1); data << uint8(0); data << uint8(0); } } session->SendPacket(&data); }