/*
* 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);
}