diff --git a/data/sql/updates/characters/2016_07_30_00.sql b/data/sql/updates/characters/2016_07_30_00.sql new file mode 100644 index 000000000..398de7c44 --- /dev/null +++ b/data/sql/updates/characters/2016_07_30_00.sql @@ -0,0 +1,25 @@ +DROP TABLE IF EXISTS `gm_ticket`; + +CREATE TABLE IF NOT EXISTS `gm_ticket` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '0 open, 1 closed, 2 character deleted', + `playerGuid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Global Unique Identifier of ticket creator', + `name` varchar(12) NOT NULL COMMENT 'Name of ticket creator', + `description` text NOT NULL, + `createTime` int(10) unsigned NOT NULL DEFAULT '0', + `mapId` smallint(5) unsigned NOT NULL DEFAULT '0', + `posX` float NOT NULL DEFAULT '0', + `posY` float NOT NULL DEFAULT '0', + `posZ` float NOT NULL DEFAULT '0', + `lastModifiedTime` int(10) unsigned NOT NULL DEFAULT '0', + `closedBy` int(10) unsigned NOT NULL DEFAULT '0', + `assignedTo` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'GUID of admin to whom ticket is assigned', + `comment` text NOT NULL, + `response` text NOT NULL, + `completed` tinyint(3) unsigned NOT NULL DEFAULT '0', + `escalated` tinyint(3) unsigned NOT NULL DEFAULT '0', + `viewed` tinyint(3) unsigned NOT NULL DEFAULT '0', + `needMoreHelp` tinyint(3) unsigned NOT NULL DEFAULT '0', + `resolvedBy` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'GUID of GM who resolved the ticket', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10572 DEFAULT CHARSET=utf8 COMMENT='Player System'; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 4cb219073..fe90f210f 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -84,6 +84,7 @@ #include "GameObjectAI.h" #include "PoolMgr.h" #include "SavingSystem.h" +#include "TicketMgr.h" #define ZONE_UPDATE_INTERVAL (2*IN_MILLISECONDS) @@ -4729,6 +4730,11 @@ void Player::DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmC // remove from arena teams LeaveAllArenaTeams(playerguid); + // close player ticket if any + GmTicket* ticket = sTicketMgr->GetTicketByPlayer(playerguid); + if (ticket) + ticket->SetClosedBy(playerguid); + // remove from group if (uint32 groupId = GetGroupIdFromStorage(guid)) if (Group* group = sGroupMgr->GetGroupByGUID(groupId)) @@ -4923,9 +4929,18 @@ void Player::DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmC stmt->setUInt32(0, guid); trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_GM_TICKETS); - stmt->setUInt32(0, guid); - trans->Append(stmt); + if (sWorld->getBoolConfig(CONFIG_DELETE_CHARACTER_TICKET_TRACE)) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_PLAYER_GM_TICKETS_ON_CHAR_DELETION); + stmt->setUInt32(0, guid); + trans->Append(stmt); + } + else + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_GM_TICKETS); + stmt->setUInt32(0, guid); + trans->Append(stmt); + } stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE_BY_OWNER); stmt->setUInt32(0, guid); diff --git a/src/server/game/Tickets/TicketMgr.cpp b/src/server/game/Tickets/TicketMgr.cpp index be4b71f87..dcff11997 100644 --- a/src/server/game/Tickets/TicketMgr.cpp +++ b/src/server/game/Tickets/TicketMgr.cpp @@ -32,11 +32,11 @@ inline float GetAge(uint64 t) { return float(time(NULL) - t) / DAY; } /////////////////////////////////////////////////////////////////////////////////////////////////// // GM ticket -GmTicket::GmTicket() : _id(0), _playerGuid(0), _posX(0), _posY(0), _posZ(0), _mapId(0), _createTime(0), _lastModifiedTime(0), - _closedBy(0), _assignedTo(0), _completed(false), _escalatedStatus(TICKET_UNASSIGNED), _viewed(false), +GmTicket::GmTicket() : _id(0), _type(TICKET_TYPE_OPEN), _playerGuid(0), _posX(0), _posY(0), _posZ(0), _mapId(0), _createTime(0), _lastModifiedTime(0), + _closedBy(0), _resolvedBy(0), _assignedTo(0), _completed(false), _escalatedStatus(TICKET_UNASSIGNED), _viewed(false), _needResponse(false), _needMoreHelp(false) { } -GmTicket::GmTicket(Player* player) : _createTime(time(NULL)), _lastModifiedTime(time(NULL)), _closedBy(0), _assignedTo(0), _completed(false), _escalatedStatus(TICKET_UNASSIGNED), _viewed(false), _needMoreHelp(false) +GmTicket::GmTicket(Player* player) : _type(TICKET_TYPE_OPEN), _createTime(time(NULL)), _lastModifiedTime(time(NULL)), _closedBy(0), _resolvedBy(0), _assignedTo(0), _completed(false), _escalatedStatus(TICKET_UNASSIGNED), _viewed(false), _needMoreHelp(false) { _id = sTicketMgr->GenerateTicketId(); _playerName = player->GetName(); @@ -47,10 +47,11 @@ 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 + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + // id, type, playerGuid, name, message, createTime, mapId, posX, posY, posZ, lastModifiedTime, closedBy, assignedTo, comment, response, completed, escalated, viewed, haveTicket, resolvedBy uint8 index = 0; _id = fields[ index].GetUInt32(); + _type = TicketType(fields[++index].GetUInt8()); _playerGuid = MAKE_NEW_GUID(fields[++index].GetUInt32(), 0, HIGHGUID_PLAYER); _playerName = fields[++index].GetString(); _message = fields[++index].GetString(); @@ -68,16 +69,18 @@ bool GmTicket::LoadFromDB(Field* fields) _escalatedStatus = GMTicketEscalationStatus(fields[++index].GetUInt8()); _viewed = fields[++index].GetBool(); _needMoreHelp = fields[++index].GetBool(); + _resolvedBy = fields[++index].GetInt32(); 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 + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + // id, type, playerGuid, name, description, createTime, mapId, posX, posY, posZ, lastModifiedTime, closedBy, assignedTo, comment, response, completed, escalated, viewed, needMoreHelp, resolvedBy uint8 index = 0; PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_GM_TICKET); stmt->setUInt32( index, _id); + stmt->setUInt8(++index, uint8(_type)); stmt->setUInt32(++index, GUID_LOPART(_playerGuid)); stmt->setString(++index, _playerName); stmt->setString(++index, _message); @@ -95,6 +98,7 @@ void GmTicket::SaveToDB(SQLTransaction& trans) const stmt->setUInt8 (++index, uint8(_escalatedStatus)); stmt->setBool (++index, _viewed); stmt->setBool (++index, _needMoreHelp); + stmt->setInt32(++index, GUID_LOPART(_resolvedBy)); CharacterDatabase.ExecuteOrAppend(trans, stmt); } @@ -363,6 +367,20 @@ void TicketMgr::RemoveTicket(uint32 ticketId) } } +void TicketMgr::ResolveAndCloseTicket(uint32 ticketId, int64 source) +{ + if (GmTicket* ticket = GetTicket(ticketId)) + { + SQLTransaction trans = SQLTransaction(nullptr); + ticket->SetClosedBy(source); + ticket->SetResolvedBy(source); + if (source) + --_openTicketCount; + ticket->SaveToDB(trans); + } +} + + void TicketMgr::ShowList(ChatHandler& handler, bool onlineOnly) const { handler.SendSysMessage(onlineOnly ? LANG_COMMAND_TICKETSHOWONLINELIST : LANG_COMMAND_TICKETSHOWLIST); diff --git a/src/server/game/Tickets/TicketMgr.h b/src/server/game/Tickets/TicketMgr.h index 86ebcd999..232827803 100644 --- a/src/server/game/Tickets/TicketMgr.h +++ b/src/server/game/Tickets/TicketMgr.h @@ -78,6 +78,13 @@ enum LagReportType LAG_REPORT_TYPE_SPELL = 6 }; +enum TicketType +{ + TICKET_TYPE_OPEN = 0, + TICKET_TYPE_CLOSED = 1, + TICKET_TYPE_CHARACTER_DELETED = 2, +}; + class GmTicket { public: @@ -85,7 +92,7 @@ public: GmTicket(Player* player); ~GmTicket(); - bool IsClosed() const { return _closedBy; } + bool IsClosed() const { return _type != TICKET_TYPE_OPEN; } bool IsCompleted() const { return _completed; } bool IsFromPlayer(uint64 guid) const { return guid == _playerGuid; } bool IsAssigned() const { return _assignedTo != 0; } @@ -119,7 +126,8 @@ public: else if (_escalatedStatus == TICKET_UNASSIGNED) _escalatedStatus = TICKET_ASSIGNED; } - void SetClosedBy(int64 value) { _closedBy = value; } + void SetClosedBy(int64 value) { _closedBy = value; _type = TICKET_TYPE_CLOSED; } + void SetResolvedBy(int64 value) { _resolvedBy = value; } void SetCompleted() { _completed = true; } void SetMessage(std::string const& message) { @@ -151,6 +159,7 @@ public: private: uint32 _id; uint64 _playerGuid; + TicketType _type; // 0 = Open, 1 = Closed, 2 = Character deleted std::string _playerName; float _posX; float _posY; @@ -159,7 +168,8 @@ private: std::string _message; uint64 _createTime; uint64 _lastModifiedTime; - int64 _closedBy; // 0 = Open, -1 = Console, playerGuid = player abandoned ticket, other = GM who closed it. + int64 _closedBy; // 0 = Open or Closed by Console (if type = 1), playerGuid = GM who closed it or player abandoned ticket or read the GM response message. + int64 _resolvedBy; // 0 = Open, -1 = Resolved by Console, GM who resolved it by closing or completing the ticket. uint64 _assignedTo; std::string _comment; bool _completed; @@ -213,6 +223,7 @@ public: void AddTicket(GmTicket* ticket); void CloseTicket(uint32 ticketId, int64 source = -1); + void ResolveAndCloseTicket(uint32 ticketId, int64 source); // used when GM resolves a ticket by simply closing it void RemoveTicket(uint32 ticketId); bool GetStatus() const { return _status; } diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 0bfd8ca3a..201c4e38c 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -443,6 +443,7 @@ void World::LoadConfigSettings(bool reload) ///- Read ticket system setting from the config file m_bool_configs[CONFIG_ALLOW_TICKETS] = sConfigMgr->GetBoolDefault("AllowTickets", true); + m_bool_configs[CONFIG_DELETE_CHARACTER_TICKET_TRACE] = sConfigMgr->GetBoolDefault("DeletedCharacterTicketTrace", false); ///- Get string for new logins (newly created characters) SetNewCharString(sConfigMgr->GetStringDefault("PlayerStart.String", "")); @@ -1043,6 +1044,7 @@ void World::LoadConfigSettings(bool reload) m_float_configs[CONFIG_LISTEN_RANGE_YELL] = sConfigMgr->GetFloatDefault("ListenRange.Yell", 300.0f); m_bool_configs[CONFIG_BATTLEGROUND_CAST_DESERTER] = sConfigMgr->GetBoolDefault("Battleground.CastDeserter", true); + m_bool_configs[CONFIG_BATTLEGROUND_RANDOM_CROSSFACTION] = sConfigMgr->GetBoolDefault("Battleground.RandomCrossFaction.Enable", true); // [AZTH] RBG Crossfaction m_bool_configs[CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE] = sConfigMgr->GetBoolDefault("Battleground.QueueAnnouncer.Enable", false); m_int_configs[CONFIG_BATTLEGROUND_PREMATURE_FINISH_TIMER] = sConfigMgr->GetIntDefault ("Battleground.PrematureFinishTimer", 5 * MINUTE * IN_MILLISECONDS); m_int_configs[CONFIG_BATTLEGROUND_PREMADE_GROUP_WAIT_FOR_MATCH] = sConfigMgr->GetIntDefault ("Battleground.PremadeGroupWaitForMatch", 30 * MINUTE * IN_MILLISECONDS); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index dfb5f7102..cbe8d5577 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -127,6 +127,7 @@ enum WorldBoolConfigs CONFIG_DIE_COMMAND_MODE, CONFIG_DECLINED_NAMES_USED, CONFIG_BATTLEGROUND_CAST_DESERTER, + CONFIG_BATTLEGROUND_RANDOM_CROSSFACTION, // [AZTH] RBG Crossfaction CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE, CONFIG_BG_XP_FOR_KILL, CONFIG_ARENA_AUTO_DISTRIBUTE_POINTS, @@ -143,6 +144,7 @@ enum WorldBoolConfigs CONFIG_SHOW_KICK_IN_WORLD, CONFIG_AUTOBROADCAST, CONFIG_ALLOW_TICKETS, + CONFIG_DELETE_CHARACTER_TICKET_TRACE, CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES, CONFIG_PRESERVE_CUSTOM_CHANNELS, CONFIG_WINTERGRASP_ENABLE, diff --git a/src/server/scripts/Commands/cs_ticket.cpp b/src/server/scripts/Commands/cs_ticket.cpp index 24a7e9c00..d36ae4902 100644 --- a/src/server/scripts/Commands/cs_ticket.cpp +++ b/src/server/scripts/Commands/cs_ticket.cpp @@ -156,7 +156,7 @@ public: return true; } - sTicketMgr->CloseTicket(ticket->GetId(), player ? player->GetGUID() : -1); + sTicketMgr->ResolveAndCloseTicket(ticket->GetId(), player ? player->GetGUID() : -1); sTicketMgr->UpdateLastChange(); std::string msg = ticket->FormatMessageString(*handler, player ? player->GetName().c_str() : "Console", NULL, NULL, NULL); @@ -234,8 +234,12 @@ public: if (Player* player = ticket->GetPlayer()) ticket->SendResponse(player->GetSession()); + Player* gm = handler->GetSession() ? handler->GetSession()->GetPlayer() : NULL; + + SQLTransaction trans = SQLTransaction(NULL); ticket->SetCompleted(); + ticket->SetResolvedBy(gm ? gm->GetGUID() : -1); ticket->SaveToDB(trans); sTicketMgr->UpdateLastChange(); diff --git a/src/server/shared/Database/Implementation/CharacterDatabase.cpp b/src/server/shared/Database/Implementation/CharacterDatabase.cpp index 80ef19aea..303ff7089 100644 --- a/src/server/shared/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/shared/Database/Implementation/CharacterDatabase.cpp @@ -302,10 +302,11 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_GO_RESPAWN_BY_INSTANCE, "DELETE FROM gameobject_respawn WHERE mapId = ? AND instanceId = ?", CONNECTION_ASYNC); // GM Tickets - PrepareStatement(CHAR_SEL_GM_TICKETS, "SELECT ticketId, guid, name, message, createTime, mapId, posX, posY, posZ, lastModifiedTime, closedBy, assignedTo, comment, response, completed, escalated, viewed, haveTicket FROM gm_tickets", CONNECTION_SYNCH); - PrepareStatement(CHAR_REP_GM_TICKET, "REPLACE INTO gm_tickets (ticketId, guid, name, message, createTime, mapId, posX, posY, posZ, lastModifiedTime, closedBy, assignedTo, comment, response, completed, escalated, viewed, haveTicket) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_GM_TICKET, "DELETE FROM gm_tickets WHERE ticketId = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_PLAYER_GM_TICKETS, "DELETE FROM gm_tickets WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_GM_TICKETS, "SELECT id, type, playerGuid, name, description, createTime, mapId, posX, posY, posZ, lastModifiedTime, closedBy, assignedTo, comment, response, completed, escalated, viewed, needMoreHelp FROM gm_ticket", CONNECTION_SYNCH); + PrepareStatement(CHAR_REP_GM_TICKET, "REPLACE INTO gm_ticket (id, type, playerGuid, name, description, createTime, mapId, posX, posY, posZ, lastModifiedTime, closedBy, assignedTo, comment, response, completed, escalated, viewed, needMoreHelp, resolvedBy) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_GM_TICKET, "DELETE FROM gm_ticket WHERE id = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_PLAYER_GM_TICKETS, "DELETE FROM gm_ticket WHERE playerGuid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_PLAYER_GM_TICKETS_ON_CHAR_DELETION, "UPDATE gm_ticket SET type = 2 WHERE playerGuid = ?", CONNECTION_ASYNC); // GM Survey/subsurvey/lag report PrepareStatement(CHAR_INS_GM_SURVEY, "INSERT INTO gm_surveys (guid, surveyId, mainSurvey, overallComment, createTime) VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(NOW()))", CONNECTION_ASYNC); @@ -349,7 +350,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UPD_GROUP_MEMBER_FLAG, "UPDATE group_member SET memberFlags = ? WHERE memberGuid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_GROUP_DIFFICULTY, "UPDATE groups SET difficulty = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_GROUP_RAID_DIFFICULTY, "UPDATE groups SET raiddifficulty = ? WHERE guid = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_ALL_GM_TICKETS, "TRUNCATE TABLE gm_tickets", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_ALL_GM_TICKETS, "TRUNCATE TABLE gm_ticket", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_INVALID_SPELL_TALENTS, "DELETE FROM character_talent WHERE spell = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_INVALID_SPELL_SPELLS, "DELETE FROM character_spell WHERE spell = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_DELETE_INFO, "UPDATE characters SET deleteInfos_Name = name, deleteInfos_Account = account, deleteDate = UNIX_TIMESTAMP(), name = '', account = 0 WHERE guid = ?", CONNECTION_ASYNC); diff --git a/src/server/shared/Database/Implementation/CharacterDatabase.h b/src/server/shared/Database/Implementation/CharacterDatabase.h index da9598e96..0a25b7751 100644 --- a/src/server/shared/Database/Implementation/CharacterDatabase.h +++ b/src/server/shared/Database/Implementation/CharacterDatabase.h @@ -268,6 +268,7 @@ enum CharacterDatabaseStatements CHAR_DEL_GM_TICKET, CHAR_DEL_ALL_GM_TICKETS, CHAR_DEL_PLAYER_GM_TICKETS, + CHAR_UPD_PLAYER_GM_TICKETS_ON_CHAR_DELETION, CHAR_INS_GM_SURVEY, CHAR_INS_GM_SUBSURVEY, diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 048ffb8fa..86efd0b7e 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -1407,6 +1407,15 @@ Command.LookupMaxResults = 0 AllowTickets = 1 +# DeletedCharacterTicketTrace +# Description: Keep trace of tickets opened by deleted characters +# gm_ticket.playerGuid will be 0, old GUID and character name +# will be included in gm_ticket.comment +# Default: 0 - (Disabled) +# 1 - (Enabled) + +DeletedCharacterTicketTrace = 0 + # # DungeonFinder.OptionsMask # Description: Dungeon and raid finder system. @@ -2439,6 +2448,14 @@ AutoBroadcast.Timer = 60000 Battleground.CastDeserter = 1 +# +# Battleground.RandomCrossFaction.Enable +# Description: Enable random battleground crossfaction randomizing system +# Default: 0 - (Disabled) +# 1 - (Enabled) + +Battleground.RandomCrossFaction.Enable = 1 + # # Battleground.QueueAnnouncer.Enable # Description: Announce battleground queue status to chat.