diff options
-rw-r--r-- | sql/base/characters_database.sql | 3 | ||||
-rw-r--r-- | sql/base/world_database.sql | 17 | ||||
-rw-r--r-- | sql/updates/8971_characters_characters.sql | 5 | ||||
-rw-r--r-- | sql/updates/8971_world_command.sql | 8 | ||||
-rw-r--r-- | src/server/game/Accounts/AccountMgr.cpp | 14 | ||||
-rw-r--r-- | src/server/game/Accounts/AccountMgr.h | 1 | ||||
-rw-r--r-- | src/server/game/Chat/Chat.cpp | 14 | ||||
-rw-r--r-- | src/server/game/Chat/Chat.h | 22 | ||||
-rw-r--r-- | src/server/game/Chat/Commands/Level3.cpp | 2 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 310 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 13 | ||||
-rw-r--r-- | src/server/game/Miscellaneous/Language.h | 13 | ||||
-rw-r--r-- | src/server/game/Tools/PlayerDump.cpp | 17 | ||||
-rw-r--r-- | src/server/game/World/World.cpp | 16 | ||||
-rw-r--r-- | src/server/game/World/World.h | 6 | ||||
-rw-r--r-- | src/server/shared/Utilities/Util.h | 9 | ||||
-rw-r--r-- | src/server/worldserver/CommandLine/CliRunnable.cpp | 318 | ||||
-rw-r--r-- | src/server/worldserver/worldserver.conf.dist | 27 |
18 files changed, 673 insertions, 142 deletions
diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index 6ec8cd968e9..4dc67ef97a1 100644 --- a/sql/base/characters_database.sql +++ b/sql/base/characters_database.sql @@ -392,6 +392,9 @@ CREATE TABLE `characters` ( `ammoId` int(10) UNSIGNED NOT NULL default '0', `knownTitles` longtext, `actionBars` tinyint(3) UNSIGNED NOT NULL default '0', + `deleteInfos_Account` int(11) UNSIGNED default NULL, + `deleteInfos_Name` varchar(12) default NULL, + `deleteDate` bigint(20) default NULL, PRIMARY KEY (`guid`), KEY `idx_account` (`account`), KEY `idx_online` (`online`), diff --git a/sql/base/world_database.sql b/sql/base/world_database.sql index 33fe9611d87..0b870ec0b8b 100644 --- a/sql/base/world_database.sql +++ b/sql/base/world_database.sql @@ -350,7 +350,11 @@ INSERT INTO `command` VALUES ('cast self',3,'Syntax: .cast self #spellid [triggered]\r\nCast #spellid by target at target itself. If ''trigered'' or part provided then spell casted with triggered flag.'), ('cast target',3,'Syntax: .cast target #spellid [triggered]\r\n Selected target will cast #spellid to his victim. If ''trigered'' or part provided then spell casted with triggered flag.'), ('character customize',2,'Syntax: .character customize [$name]\r\n\r\nMark selected in game or by $name in command character for customize at next login.'), -('character delete',4,'Syntax: .character delete $name\r\n\r\nDelete character $name.'), +('character erase',4,'Syntax: .character erase $name\r\n\r\nDelete character $name. Character finally deleted in case any deleting options.'), +('character deleted delete', 4, 'Syntax: .character deleted delete #guid|$name\r\n\r\nCompletely deletes the selected characters.\r\nIf $name is supplied, only characters with that string in their name will be deleted, if #guid is supplied, only the character with that GUID will be deleted.'), +('character deleted list', 3, 'Syntax: .character deleted list [#guid|$name]\r\n\r\nShows a list with all deleted characters.\r\nIf $name is supplied, only characters with that string in their name will be selected, if #guid is supplied, only the character with that GUID will be selected.'), +('character deleted old', 4, 'Syntax: .character deleted old [#keepDays]\r\n\r\nCompletely deletes all characters with deleted time longer #keepDays. If #keepDays not provided the used value from mangosd.conf option \'CharDelete.KeepDays\'. If referenced config option disabled (use 0 value) then command can\'t be used without #keepDays.'), +('character deleted restore', 3, 'Syntax: .character deleted restore #guid|$name [$newname] [#new account]\r\n\r\nRestores deleted characters.\r\nIf $name is supplied, only characters with that string in their name will be restored, if $guid is supplied, only the character with that GUID will be restored.\r\nIf $newname is set, the character will be restored with that name instead of the original one. If #newaccount is set, the character will be restored to specific account character list. This works only with one character!'); ('character level',3,'Syntax: .character level [$playername] [#level]\r\n\r\nSet the level of character with $playername (or the selected if not name provided) by #numberoflevels Or +1 if no #numberoflevels provided). If #numberoflevels is omitted, the level will be increase by 1. If #numberoflevels is 0, the same level will be restarted. If no character is selected and name not provided, increase your level. Command can be used for offline character. All stats and dependent values recalculated. At level decrease talents can be reset if need. Also at level decrease equipped items with greater level requirement can be lost.'), ('character rename',2,'Syntax: .character rename [$name]\r\n\r\nMark selected in game or by $name in command character for rename at next login.'), ('character reputation',2,'Syntax: .character reputation [$player_name]\r\n\r\nShow reputation information for selected player or player find by $player_name.'), @@ -15252,6 +15256,17 @@ INSERT INTO `trinity_string` (`entry`,`content_default`,`content_loc1`,`content_ (1013, '-[%16s][%12s][%15s][%5d][%9d][%3d][%4d]', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), (1014, 'No online players.', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), (1015, '============================== Characters Online =============================', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(1016, '| GUID | Name | Account | Delete Date |', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(1017, '| %10u | %20s | %15s (%10u) | %19s |', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(1018, '==========================================================================================', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(1019, 'No characters found.', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(1020, 'Restoring the following characters:', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(1021, 'Deleting the following characters:', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(1022, 'ERROR: You can only assign a new name if you have only selected a single character!', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(1023, 'Character \'%s\' (GUID: %u Account %u) can\'t be restored: account not exist!', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(1024, 'Character \'%s\' (GUID: %u Account %u) can\'t be restored: account character list full!', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(1025, 'Character \'%s\' (GUID: %u Account %u) can\'t be restored: new name already used!', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(1026, 'GUID: %u Name: %s Account: %s (%u) Date: %s', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); (1100, 'Account %s (Id: %u) have up to %u expansion allowed now.', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), (1101, 'Message of the day changed to:\r\n%s', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), (1102, 'Message sent to %s: %s', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), diff --git a/sql/updates/8971_characters_characters.sql b/sql/updates/8971_characters_characters.sql new file mode 100644 index 00000000000..c780250a1a4 --- /dev/null +++ b/sql/updates/8971_characters_characters.sql @@ -0,0 +1,5 @@ +ALTER TABLE `characters` + ADD COLUMN `deleteInfos_Account` int(11) UNSIGNED default NULL AFTER actionBars, + ADD COLUMN `deleteInfos_Name` varchar(12) default NULL AFTER deleteInfos_Account, + ADD COLUMN `deleteDate` bigint(20) default NULL AFTER deleteInfos_Name; + diff --git a/sql/updates/8971_world_command.sql b/sql/updates/8971_world_command.sql new file mode 100644 index 00000000000..d7139386aa4 --- /dev/null +++ b/sql/updates/8971_world_command.sql @@ -0,0 +1,8 @@ +DELETE FROM command WHERE name IN ('character delete', 'character deleted list', 'character deleted restore', 'character deleted delete', 'character deleted old', 'character erase'); +INSERT INTO command (name, security, help) VALUES +('character erase',4,'Syntax: .character erase $name\r\n\r\nDelete character $name. Character finally deleted in case any deleting options.'), +('character deleted delete', 4, 'Syntax: .character deleted delete #guid|$name\r\n\r\nCompletely deletes the selected characters.\r\nIf $name is supplied, only characters with that string in their name will be deleted, if #guid is supplied, only the character with that GUID will be deleted.'), +('character deleted list', 3, 'Syntax: .character deleted list [#guid|$name]\r\n\r\nShows a list with all deleted characters.\r\nIf $name is supplied, only characters with that string in their name will be selected, if #guid is supplied, only the character with that GUID will be selected.'), +('character deleted old', 4, 'Syntax: .character deleted old [#keepDays]\r\n\r\nCompletely deletes all characters with deleted time longer #keepDays. If #keepDays not provided the used value from mangosd.conf option \'CharDelete.KeepDays\'. If referenced config option disabled (use 0 value) then command can\'t be used without #keepDays.'), +('character deleted restore', 3, 'Syntax: .character deleted restore #guid|$name [$newname] [#new account]\r\n\r\nRestores deleted characters.\r\nIf $name is supplied, only characters with that string in their name will be restored, if $guid is supplied, only the character with that GUID will be restored.\r\nIf $newname is set, the character will be restored with that name instead of the original one. If #newaccount is set, the character will be restored to specific account character list. This works only with one character!'); + diff --git a/src/server/game/Accounts/AccountMgr.cpp b/src/server/game/Accounts/AccountMgr.cpp index 54f80114131..54c4f4ffc35 100644 --- a/src/server/game/Accounts/AccountMgr.cpp +++ b/src/server/game/Accounts/AccountMgr.cpp @@ -61,6 +61,7 @@ AccountOpResult AccountMgr::DeleteAccount(uint32 accid) if (!result) return AOR_NAME_NOT_EXIST; // account doesn't exist + // existed characters list result = CharacterDatabase.PQuery("SELECT guid FROM characters WHERE account='%d'",accid); if (result) { @@ -214,6 +215,19 @@ bool AccountMgr::CheckPassword(uint32 accid, std::string passwd) return false; } +uint32 AccountMgr::GetCharactersCount(uint32 acc_id) +{ + uint32 charcount = 0; + // check character count + QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT COUNT(guid) FROM characters WHERE account = '%d'", acc_id); + if (result) + { + Field *fields=result->Fetch(); + charcount = fields[0].GetUInt32(); + } + return charcount; +} + bool AccountMgr::normalizeString(std::string& utf8str) { wchar_t wstr_buf[MAX_ACCOUNT_STR+1]; diff --git a/src/server/game/Accounts/AccountMgr.h b/src/server/game/Accounts/AccountMgr.h index a0e181cf4a0..f53d29032b7 100644 --- a/src/server/game/Accounts/AccountMgr.h +++ b/src/server/game/Accounts/AccountMgr.h @@ -53,6 +53,7 @@ class AccountMgr uint32 GetSecurity(uint32 acc_id); uint32 GetSecurity(uint32 acc_id, int32 realm_id); bool GetName(uint32 acc_id, std::string &name); + uint32 GetCharactersCount(uint32 acc_id); std::string CalculateShaPassHash(std::string& name, std::string& password); static bool normalizeString(std::string& utf8str); diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp index 42363124518..5efd4b4ef6a 100644 --- a/src/server/game/Chat/Chat.cpp +++ b/src/server/game/Chat/Chat.cpp @@ -119,10 +119,20 @@ ChatCommand * ChatHandler::getCommandTable() { NULL, 0, false, NULL, "", NULL } }; + static ChatCommand characterDeletedCommandTable[] = + { + { "delete", SEC_CONSOLE, true, &ChatHandler::HandleCharacterDeletedDeleteCommand, "", NULL }, + { "list", SEC_ADMINISTRATOR, true, &ChatHandler::HandleCharacterDeletedListCommand, "", NULL }, + { "restore", SEC_ADMINISTRATOR, true, &ChatHandler::HandleCharacterDeletedRestoreCommand, "", NULL }, + { "old", SEC_CONSOLE, true, &ChatHandler::HandleCharacterDeletedOldCommand, "", NULL }, + { NULL, 0, false, NULL, "", NULL } + }; + static ChatCommand characterCommandTable[] = { { "customize", SEC_GAMEMASTER, true, &ChatHandler::HandleCharacterCustomizeCommand, "", NULL }, - { "delete", SEC_CONSOLE, true, &ChatHandler::HandleCharacterDeleteCommand, "", NULL }, + { "deleted", SEC_GAMEMASTER, true, NULL, "", characterDeletedCommandTable}, + { "erase", SEC_CONSOLE, true, &ChatHandler::HandleCharacterEraseCommand, "", NULL }, { "level", SEC_ADMINISTRATOR, true, &ChatHandler::HandleCharacterLevelCommand, "", NULL }, { "rename", SEC_GAMEMASTER, true, &ChatHandler::HandleCharacterRenameCommand, "", NULL }, { "reputation", SEC_GAMEMASTER, true, &ChatHandler::HandleCharacterReputationCommand, "", NULL }, @@ -667,7 +677,7 @@ ChatCommand * ChatHandler::getCommandTable() { "modify", SEC_MODERATOR, false, NULL, "", modifyCommandTable }, { "debug", SEC_MODERATOR, true, NULL, "", debugCommandTable }, { "tele", SEC_MODERATOR, true, NULL, "", teleCommandTable }, - { "character", SEC_GAMEMASTER, false, NULL, "", characterCommandTable}, + { "character", SEC_GAMEMASTER, true, NULL, "", characterCommandTable}, { "event", SEC_GAMEMASTER, false, NULL, "", eventCommandTable }, { "gobject", SEC_GAMEMASTER, false, NULL, "", gobjectCommandTable }, { "honor", SEC_GAMEMASTER, false, NULL, "", honorCommandTable }, diff --git a/src/server/game/Chat/Chat.h b/src/server/game/Chat/Chat.h index 2a653983aa0..1a649c687d1 100644 --- a/src/server/game/Chat/Chat.h +++ b/src/server/game/Chat/Chat.h @@ -133,7 +133,11 @@ class ChatHandler bool HandleCastTargetCommand(const char *args); bool HandleCharacterCustomizeCommand(const char * args); - bool HandleCharacterDeleteCommand(const char* args); + bool HandleCharacterDeletedDeleteCommand(const char* args); + bool HandleCharacterDeletedListCommand(const char* args); + bool HandleCharacterDeletedRestoreCommand(const char* args); + bool HandleCharacterDeletedOldCommand(const char* args); + bool HandleCharacterEraseCommand(const char* args); bool HandleCharacterLevelCommand(const char* args); bool HandleCharacterRenameCommand(const char * args); bool HandleCharacterReputationCommand(const char* args); @@ -620,6 +624,22 @@ class ChatHandler void HandleCharacterLevel(Player* player, uint64 player_guid, uint32 oldlevel, uint32 newlevel); void HandleLearnSkillRecipesHelper(Player* player,uint32 skill_id); + // Stores informations about a deleted character + struct DeletedInfo + { + uint32 lowguid; ///< the low GUID from the character + std::string name; ///< the character name + uint32 accountId; ///< the account id + std::string accountName; ///< the account name + time_t deleteDate; ///< the date at which the character has been deleted + }; + + typedef std::list<DeletedInfo> DeletedInfoList; + bool GetDeletedCharacterInfoList(DeletedInfoList& foundList, std::string searchString = ""); + std::string GenerateDeletedCharacterGUIDsWhereStr(DeletedInfoList::const_iterator& itr, DeletedInfoList::const_iterator const& itr_end); + void HandleCharacterDeletedListHelper(DeletedInfoList const& foundList); + void HandleCharacterDeletedRestoreHelper(DeletedInfo const& delInfo); + void SetSentErrorMessage(bool val){ sentErrorMessage = val;}; private: WorldSession * m_session; // != NULL for chat command call and NULL for CLI command diff --git a/src/server/game/Chat/Commands/Level3.cpp b/src/server/game/Chat/Commands/Level3.cpp index 40c3aae5705..049df5a342f 100644 --- a/src/server/game/Chat/Commands/Level3.cpp +++ b/src/server/game/Chat/Commands/Level3.cpp @@ -6522,7 +6522,7 @@ bool ChatHandler::HandlePDumpWriteCommand(const char *args) uint32 guid; // character name can't start from number - if (isNumeric(p2[0])) + if (isNumeric(p2)) guid = atoi(p2); else { diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 94ca4bd20f6..4caa328839b 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -4343,22 +4343,41 @@ TrainerSpellState Player::GetTrainerSpellState(TrainerSpell const* trainer_spell return TRAINER_SPELL_GREEN; } -void Player::DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmChars) +/** + * Deletes a character from the database + * + * The way, how the characters will be deleted is decided based on the config option. + * + * @see Player::DeleteOldCharacters + * + * @param playerguid the low-GUID from the player which should be deleted + * @param accountId the account id from the player + * @param updateRealmChars when this flag is set, the amount of characters on that realm will be updated in the realmlist + * @param deleteFinally if this flag is set, the config option will be ignored and the character will be permanently removed from the database + */ +void Player::DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmChars, bool deleteFinally) { + // for not existed account avoid update realm + if (accountId == 0) + updateRealmChars = false; + + uint32 charDelete_method = sWorld.getConfig(CONFIG_CHARDELETE_METHOD); + uint32 charDelete_minLvl = sWorld.getConfig(CONFIG_CHARDELETE_MIN_LEVEL); + + // if we want to finally delete the character or the character does not meet the level requirement, + // we set it to mode CHAR_DELETE_REMOVE + if (deleteFinally || Player::GetLevelFromDB(playerguid) < charDelete_minLvl) + charDelete_method = CHAR_DELETE_REMOVE; + uint32 guid = GUID_LOPART(playerguid); // convert corpse to bones if exist (to prevent exiting Corpse in World without DB entry) // bones will be deleted by corpse/bones deleting thread shortly sObjectAccessor.ConvertCorpseForPlayer(playerguid); - // remove from guild - uint32 guildId = GetGuildIdFromDB(playerguid); - if (guildId != 0) - { - Guild* guild = objmgr.GetGuildById(guildId); - if (guild) + if (uint32 guildId = GetGuildIdFromDB(playerguid)) + if (Guild* guild = objmgr.GetGuildById(guildId)) guild->DelMember(guid); - } // remove from arena teams LeaveAllArenaTeams(playerguid); @@ -4372,139 +4391,194 @@ void Player::DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmC RemoveFromGroup(group, playerguid); } - // remove signs from petitions (also remove petitions if owner); + // Remove signs from petitions (also remove petitions if owner); RemovePetitionsAndSigns(playerguid, 10); - // return back all mails with COD and Item 0 1 2 3 4 5 6 7 - QueryResult_AutoPtr resultMail = CharacterDatabase.PQuery("SELECT id,messageType,mailTemplateId,sender,subject,body,money,has_items FROM mail WHERE receiver='%u' AND has_items<>0 AND cod<>0", guid); - if (resultMail) + switch (charDelete_method) { - do + // Completely remove from the database + case CHAR_DELETE_REMOVE: { - Field *fields = resultMail->Fetch(); - - uint32 mail_id = fields[0].GetUInt32(); - uint16 mailType = fields[1].GetUInt16(); - uint16 mailTemplateId= fields[2].GetUInt16(); - uint32 sender = fields[3].GetUInt32(); - std::string subject = fields[4].GetCppString(); - std::string body = fields[5].GetCppString(); - uint32 money = fields[6].GetUInt32(); - bool has_items = fields[7].GetBool(); - - //we can return mail now - //so firstly delete the old one - CharacterDatabase.PExecute("DELETE FROM mail WHERE id = '%u'", mail_id); - - // mail not from player - if (mailType != MAIL_NORMAL) - { - if (has_items) - CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mail_id); - continue; - } - - MailDraft draft(subject, body); - if (mailTemplateId) - draft = MailDraft(mailTemplateId,false); // itesm already included - - if (has_items) + // Return back all mails with COD and Item 0 1 2 3 4 5 6 7 + QueryResult_AutoPtr resultMail = CharacterDatabase.PQuery("SELECT id,messageType,mailTemplateId,sender,subject,body,money,has_items FROM mail WHERE receiver='%u' AND has_items<>0 AND cod<>0", guid); + if (resultMail) { - // data needs to be at first place for Item::LoadFromDB - QueryResult_AutoPtr resultItems = CharacterDatabase.PQuery("SELECT data,text,item_guid,item_template FROM mail_items JOIN item_instance ON item_guid = guid WHERE mail_id='%u'", mail_id); - if (resultItems) + do { - do + Field *fields = resultMail->Fetch(); + + uint32 mail_id = fields[0].GetUInt32(); + uint16 mailType = fields[1].GetUInt16(); + uint16 mailTemplateId= fields[2].GetUInt16(); + uint32 sender = fields[3].GetUInt32(); + std::string subject = fields[4].GetCppString(); + std::string body = fields[5].GetCppString(); + uint32 money = fields[6].GetUInt32(); + bool has_items = fields[7].GetBool(); + + // We can return mail now + // So firstly delete the old one + CharacterDatabase.PExecute("DELETE FROM mail WHERE id = '%u'", mail_id); + + // Mail is not from player + if (mailType != MAIL_NORMAL) { - Field *fields2 = resultItems->Fetch(); + if (has_items) + CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mail_id); + continue; + } - uint32 item_guidlow = fields2[2].GetUInt32(); - uint32 item_template = fields2[3].GetUInt32(); + MailDraft draft(subject, body); + if (mailTemplateId) + draft = MailDraft(mailTemplateId,false); // items are already included - ItemPrototype const* itemProto = objmgr.GetItemPrototype(item_template); - if (!itemProto) + if (has_items) + { + // Data needs to be at first place for Item::LoadFromDB + QueryResult_AutoPtr resultItems = CharacterDatabase.PQuery("SELECT data,text,item_guid,item_template FROM mail_items JOIN item_instance ON item_guid = guid WHERE mail_id='%u'", mail_id); + if (resultItems) { - CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid = '%u'", item_guidlow); - continue; - } + do + { + Field *fields2 = resultItems->Fetch(); - Item *pItem = NewItemOrBag(itemProto); - if (!pItem->LoadFromDB(item_guidlow, MAKE_NEW_GUID(guid, 0, HIGHGUID_PLAYER),resultItems)) - { - pItem->FSetState(ITEM_REMOVED); - pItem->SaveToDB(); // it also deletes item object ! - continue; - } + uint32 item_guidlow = fields2[2].GetUInt32(); + uint32 item_template = fields2[3].GetUInt32(); + + ItemPrototype const* itemProto = objmgr.GetItemPrototype(item_template); + if (!itemProto) + { + CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid = '%u'", item_guidlow); + continue; + } + + Item *pItem = NewItemOrBag(itemProto); + if (!pItem->LoadFromDB(item_guidlow, MAKE_NEW_GUID(guid, 0, HIGHGUID_PLAYER),resultItems)) + { + pItem->FSetState(ITEM_REMOVED); + pItem->SaveToDB(); // it also deletes item object! + continue; + } - draft.AddItem(pItem); + draft.AddItem(pItem); + } + while (resultItems->NextRow()); + } } - while (resultItems->NextRow()); + + CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mail_id); + + uint32 pl_account = objmgr.GetPlayerAccountIdByGUID(MAKE_NEW_GUID(guid, 0, HIGHGUID_PLAYER)); + + draft.AddMoney(money).SendReturnToSender(pl_account, guid, sender); } + while (resultMail->NextRow()); } - CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mail_id); - - uint32 pl_account = objmgr.GetPlayerAccountIdByGUID(MAKE_NEW_GUID(guid, 0, HIGHGUID_PLAYER)); + // Unsummon and delete for pets in world is not required: player deleted from CLI or character list with not loaded pet. + // Get guids of character's pets, will deleted in transaction + QueryResult_AutoPtr resultPets = CharacterDatabase.PQuery("SELECT id FROM character_pet WHERE owner = '%u'",guid); - draft.AddMoney(money).SendReturnToSender(pl_account, guid, sender); + // NOW we can finally clear other DB data related to character + CharacterDatabase.BeginTransaction(); + if (resultPets) + { + do + { + Field *fields3 = resultPets->Fetch(); + uint32 petguidlow = fields3[0].GetUInt32(); + Pet::DeleteFromDB(petguidlow); + } while (resultPets->NextRow()); + } + + CharacterDatabase.PExecute("DELETE FROM characters WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_account_data WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_declinedname WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_action WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_aura WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_gifts WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_homebind WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM group_instance WHERE leaderGuid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM groups WHERE leaderGuid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM group_member WHERE leaderGuid = '%u' OR memberGuid = '%u'",guid,guid); + CharacterDatabase.PExecute("DELETE FROM groups WHERE leaderGuid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_queststatus WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_reputation WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_spell WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_spell_cooldown WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM gm_tickets WHERE playerGuid = '%u'", guid); + CharacterDatabase.PExecute("DELETE FROM item_instance WHERE owner_guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_social WHERE guid = '%u' OR friend='%u'",guid,guid); + CharacterDatabase.PExecute("DELETE FROM mail WHERE receiver = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM mail_items WHERE receiver = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_pet WHERE owner = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_pet_declinedname WHERE owner = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_achievement WHERE guid = '%u' " // NOTE: These achievements have flags & 256 in DBC. + "AND achievement NOT BETWEEN '456' AND '467' " // Realm First Level 80 + "AND achievement NOT BETWEEN '1400' AND '1427' " // Realm First Raid Achievements + "AND achievement NOT IN(1463, 3117, 3259) ", guid); // Realm First Northen Vanguard + Raid Achievements + CharacterDatabase.PExecute("DELETE FROM character_achievement_progress WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_equipmentsets WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM guild_eventlog WHERE PlayerGuid1 = '%u' OR PlayerGuid2 = '%u'",guid, guid); + CharacterDatabase.PExecute("DELETE FROM guild_bank_eventlog WHERE PlayerGuid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_battleground_data WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_glyphs WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_queststatus_daily WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u'",guid); + CharacterDatabase.PExecute("DELETE FROM character_skills WHERE guid = '%u'",guid); + + CharacterDatabase.CommitTransaction(); + break; } - while (resultMail->NextRow()); + // The character gets unlinked from the account, the name gets freed up and appears as deleted ingame + case CHAR_DELETE_UNLINK: + CharacterDatabase.PExecute("UPDATE characters SET deleteInfos_Name=name, deleteInfos_Account=account, deleteDate='" UI64FMTD "', name='', account=0 WHERE guid=%u", uint64(time(NULL)), guid); + break; + default: + sLog.outError("Player::DeleteFromDB: Unsupported delete method: %u.", charDelete_method); } - // unsummon and delete for pets in world is not required: player deleted from CLI or character list with not loaded pet. - // Get guids of character's pets, will deleted in transaction - QueryResult_AutoPtr resultPets = CharacterDatabase.PQuery("SELECT id FROM character_pet WHERE owner = '%u'",guid); + if (updateRealmChars) + sWorld.UpdateRealmCharCount(accountId); +} - // NOW we can finally clear other DB data related to character - CharacterDatabase.BeginTransaction(); - if (resultPets) - { - do - { - Field *fields3 = resultPets->Fetch(); - uint32 petguidlow = fields3[0].GetUInt32(); - Pet::DeleteFromDB(petguidlow); - } while (resultPets->NextRow()); - } - - CharacterDatabase.PExecute("DELETE FROM characters WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_account_data WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_declinedname WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_action WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_aura WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_gifts WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_homebind WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_instance WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_queststatus WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_reputation WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_spell WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_spell_cooldown WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM gm_tickets WHERE playerGuid = '%u'", guid); - CharacterDatabase.PExecute("DELETE FROM item_instance WHERE owner_guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_social WHERE guid = '%u' OR friend='%u'",guid,guid); - CharacterDatabase.PExecute("DELETE FROM mail WHERE receiver = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM mail_items WHERE receiver = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_pet WHERE owner = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_pet_declinedname WHERE owner = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_achievement WHERE guid = '%u' " // NOTE: These achievements have flags & 256 in DBC. - "AND achievement NOT BETWEEN '456' AND '467' " // Realm First Level 80 - "AND achievement NOT BETWEEN '1400' AND '1427' " // Realm First Raid Achievements - "AND achievement NOT IN(1463, 3117, 3259) ", guid); // Realm First Northen Vanguard + Raid Achievements - CharacterDatabase.PExecute("DELETE FROM character_achievement_progress WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_equipmentsets WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM guild_eventlog WHERE PlayerGuid1 = '%u' OR PlayerGuid2 = '%u'",guid, guid); - CharacterDatabase.PExecute("DELETE FROM guild_bank_eventlog WHERE PlayerGuid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_battleground_data WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_glyphs WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_queststatus_daily WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_talent WHERE guid = '%u'",guid); - CharacterDatabase.PExecute("DELETE FROM character_skills WHERE guid = '%u'",guid); +/** + * Characters which were kept back in the database after being deleted and are now too old (see config option "CharDelete.KeepDays"), will be completely deleted. + * + * @see Player::DeleteFromDB + */ +void Player::DeleteOldCharacters() +{ + uint32 keepDays = sWorld.getConfig(CONFIG_CHARDELETE_KEEP_DAYS); + if (!keepDays) + return; - CharacterDatabase.CommitTransaction(); + Player::DeleteOldCharacters(keepDays); +} - //LoginDatabase.PExecute("UPDATE realmcharacters SET numchars = numchars - 1 WHERE acctid = %d AND realmid = %d", accountId, realmID); - if (updateRealmChars) sWorld.UpdateRealmCharCount(accountId); +/** + * Characters which were kept back in the database after being deleted and are older than the specified amount of days, will be completely deleted. + * + * @see Player::DeleteFromDB + * + * @param keepDays overrite the config option by another amount of days + */ +void Player::DeleteOldCharacters(uint32 keepDays) +{ + sLog.outString("Player::DeleteOldChars: Deleting all characters which have been deleted %u days before...", keepDays); + + QueryResult_AutoPtr resultChars = CharacterDatabase.PQuery("SELECT guid, deleteInfos_Account FROM characters WHERE deleteDate IS NOT NULL AND deleteDate < '" UI64FMTD "'", uint64(time(NULL) - time_t(keepDays * DAY))); + if (resultChars) + { + sLog.outString("Player::DeleteOldChars: Found %u character(s) to delete",resultChars->GetRowCount()); + do + { + Field *charFields = resultChars->Fetch(); + Player::DeleteFromDB(charFields[0].GetUInt64(), charFields[1].GetUInt32(), true, true); + } while(resultChars->NextRow()); + } } void Player::SetMovement(PlayerMovementType pType) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 97c1031899d..d568d226042 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -839,6 +839,13 @@ struct AccessRequirement std::string heroicQuestFailedText; }; +enum CharDeleteMethod +{ + CHAR_DELETE_REMOVE = 0, // Completely remove from the database + CHAR_DELETE_UNLINK = 1 // The character gets unlinked from the account, + // the name gets freed up and appears as deleted ingame +}; + class PlayerTaxi { public: @@ -1418,6 +1425,10 @@ class Player : public Unit, public GridObject<Player> static void Customize(uint64 guid, uint8 gender, uint8 skin, uint8 face, uint8 hairStyle, uint8 hairColor, uint8 facialHair); static void SavePositionInDB(uint32 mapid, float x,float y,float z,float o,uint32 zone,uint64 guid); + static void DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmChars = true, bool deleteFinally = false); + static void DeleteOldCharacters(); + static void DeleteOldCharacters(uint32 keepDays); + bool m_mailsLoaded; bool m_mailsUpdated; @@ -1836,8 +1847,6 @@ class Player : public Unit, public GridObject<Player> void SendTeleportPacket(Position &oldPos); void SendTeleportAckPacket(); - static void DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmChars = true); - Corpse *GetCorpse() const; void SpawnCorpseBones(); void CreateCorpse(); diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 435c6549f56..abf92a14b45 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -748,7 +748,18 @@ enum TrinityStrings LANG_ACCOUNT_LIST_LINE = 1013, LANG_ACCOUNT_LIST_EMPTY = 1014, LANG_ACCOUNT_LIST_BAR_HEADER = 1015, - // Room for more level 4 1016-1099 not used + LANG_CHARACTER_DELETED_LIST_HEADER = 1016, + LANG_CHARACTER_DELETED_LIST_LINE_CONSOLE = 1017, + LANG_CHARACTER_DELETED_LIST_BAR = 1018, + LANG_CHARACTER_DELETED_LIST_EMPTY = 1019, + LANG_CHARACTER_DELETED_RESTORE = 1020, + LANG_CHARACTER_DELETED_DELETE = 1021, + LANG_CHARACTER_DELETED_ERR_RENAME = 1022, + LANG_CHARACTER_DELETED_SKIP_ACCOUNT = 1023, + LANG_CHARACTER_DELETED_SKIP_FULL = 1024, + LANG_CHARACTER_DELETED_SKIP_NAME = 1025, + LANG_CHARACTER_DELETED_LIST_LINE_CHAT = 1026, + // Room for more level 4 1027-1099 not used // Level 3 (continue) LANG_ACCOUNT_SETADDON = 1100, diff --git a/src/server/game/Tools/PlayerDump.cpp b/src/server/game/Tools/PlayerDump.cpp index 964e7f9b3b1..79b2ae0b1ff 100644 --- a/src/server/game/Tools/PlayerDump.cpp +++ b/src/server/game/Tools/PlayerDump.cpp @@ -24,6 +24,7 @@ #include "SQLStorage.h" #include "UpdateFields.h" #include "ObjectMgr.h" +#include "AccountMgr.h" #define DUMP_TABLE_COUNT 26 struct DumpTable @@ -359,19 +360,9 @@ DumpReturn PlayerDumpWriter::WriteDump(const std::string& file, uint32 guid) DumpReturn PlayerDumpReader::LoadDump(const std::string& file, uint32 account, std::string name, uint32 guid) { - // check character count - { - QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT COUNT(guid) FROM characters WHERE account = '%d'", account); - uint8 charcount = 0; - if (result) - { - Field *fields=result->Fetch(); - charcount = fields[0].GetUInt8(); - - if (charcount >= 10) - return DUMP_TOO_MANY_CHARS; - } - } + uint32 charcount = accmgr.GetCharactersCount(account); + if (charcount >= 10) + return DUMP_TOO_MANY_CHARS; FILE *fin = fopen(file.c_str(), "r"); if (!fin) diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index ff7b8a472c2..0c5505fa16a 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1172,6 +1172,11 @@ void World::LoadConfigSettings(bool reload) m_visibility_notify_periodInInstances = sConfig.GetIntDefault("Visibility.Notify.Period.InInstances", DEFAULT_VISIBILITY_NOTIFY_PERIOD); m_visibility_notify_periodInBGArenas = sConfig.GetIntDefault("Visibility.Notify.Period.InBGArenas", DEFAULT_VISIBILITY_NOTIFY_PERIOD); + ///- Load the CharDelete related config options + m_configs[CONFIG_CHARDELETE_METHOD] = sConfig.GetIntDefault("CharDelete.Method", 0); + m_configs[CONFIG_CHARDELETE_MIN_LEVEL] = sConfig.GetIntDefault("CharDelete.MinLevel", 0); + m_configs[CONFIG_CHARDELETE_KEEP_DAYS] = sConfig.GetIntDefault("CharDelete.KeepDays", 30); + ///- Read the "Data" directory from the config file std::string dataPath = sConfig.GetStringDefault("DataDir","./"); if (dataPath.at(dataPath.length()-1) != '/' && dataPath.at(dataPath.length()-1) != '\\') @@ -1639,6 +1644,7 @@ void World::SetInitialWorldSettings() m_timers[WUPDATE_CLEANDB].SetInterval(m_configs[CONFIG_LOGDB_CLEARINTERVAL]*MINUTE*IN_MILLISECONDS); // clean logs table every 14 days by default m_timers[WUPDATE_AUTOBROADCAST].SetInterval(abtimer); + m_timers[WUPDATE_DELETECHARS].SetInterval(DAY*IN_MILLISECONDS); // check for chars to delete every day //to set mailtimer to return mails every day between 4 and 5 am //mailtimer is increased when updating auctions @@ -1660,6 +1666,9 @@ void World::SetInitialWorldSettings() uint32 nextGameEvent = gameeventmgr.Initialize(); m_timers[WUPDATE_EVENTS].SetInterval(nextGameEvent); //depend on next event + // Delete all characters which have been deleted X days before + Player::DeleteOldCharacters(); + sLog.outString("Starting Arena Season..."); gameeventmgr.StartArenaSeason(); @@ -1958,6 +1967,13 @@ void World::Update(uint32 diff) sOutdoorPvPMgr.Update(diff); RecordTimeDiff("UpdateOutdoorPvPMgr"); + ///- Delete all characters which have been deleted X days before + if (m_timers[WUPDATE_DELETECHARS].Passed()) + { + m_timers[WUPDATE_DELETECHARS].Reset(); + Player::DeleteOldCharacters(); + } + sLFGMgr.Update(diff); RecordTimeDiff("UpdateLFGMgr"); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index f3a92108f60..88c845213a2 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -84,7 +84,8 @@ enum WorldTimers WUPDATE_CLEANDB = 7, WUPDATE_AUTOBROADCAST = 8, WUPDATE_MAILBOXQUEUE = 9, - WUPDATE_COUNT = 10 + WUPDATE_DELETECHARS = 10, + WUPDATE_COUNT = 11 }; /// Configuration elements @@ -281,6 +282,9 @@ enum WorldConfigs CONFIG_BG_XP_FOR_KILL, CONFIG_RANDOM_BG_RESET_HOUR, CONFIG_VMAP_INDOOR_CHECK, + CONFIG_CHARDELETE_KEEP_DAYS, + CONFIG_CHARDELETE_METHOD, + CONFIG_CHARDELETE_MIN_LEVEL, CONFIG_VALUE_COUNT }; diff --git a/src/server/shared/Utilities/Util.h b/src/server/shared/Utilities/Util.h index 4f997725d25..68886bb0d42 100644 --- a/src/server/shared/Utilities/Util.h +++ b/src/server/shared/Utilities/Util.h @@ -182,6 +182,15 @@ inline bool isNumeric(char c) return (c >= '0' && c <='9'); } +inline bool isNumeric(char const* str) +{ + for (char const* c = str; *c; ++c) + if (!isNumeric(*c)) + return false; + + return true; +} + inline bool isNumericOrSpace(wchar_t wchar) { return isNumeric(wchar) || wchar == L' '; diff --git a/src/server/worldserver/CommandLine/CliRunnable.cpp b/src/server/worldserver/CommandLine/CliRunnable.cpp index c72e454700c..23a023972c9 100644 --- a/src/server/worldserver/CommandLine/CliRunnable.cpp +++ b/src/server/worldserver/CommandLine/CliRunnable.cpp @@ -163,8 +163,322 @@ bool ChatHandler::HandleAccountDeleteCommand(const char* args) return true; } -bool ChatHandler::HandleCharacterDeleteCommand(const char* args) +/** + * Collects all GUIDs (and related info) from deleted characters which are still in the database. + * + * @param foundList a reference to an std::list which will be filled with info data + * @param searchString the search string which either contains a player GUID or a part fo the character-name + * @return returns false if there was a problem while selecting the characters (e.g. player name not normalizeable) + */ +bool ChatHandler::GetDeletedCharacterInfoList(DeletedInfoList& foundList, std::string searchString) +{ + QueryResult_AutoPtr resultChar; + if (!searchString.empty()) + { + // search by GUID + if (isNumeric(searchString.c_str())) + resultChar = CharacterDatabase.PQuery("SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND guid = %u", uint64(atoi(searchString.c_str()))); + // search by name + else + { + if(!normalizePlayerName(searchString)) + return false; + + resultChar = CharacterDatabase.PQuery("SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND deleteInfos_Name " _LIKE_ " " _CONCAT3_("'%%'", "'%s'", "'%%'"), searchString.c_str()); + } + } + else + resultChar = CharacterDatabase.Query("SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL"); + + if (resultChar) + { + do + { + Field* fields = resultChar->Fetch(); + + DeletedInfo info; + + info.lowguid = fields[0].GetUInt32(); + info.name = fields[1].GetCppString(); + info.accountId = fields[2].GetUInt32(); + + // account name will be empty for not existed account + accmgr.GetName(info.accountId, info.accountName); + + info.deleteDate = time_t(fields[3].GetUInt64()); + + foundList.push_back(info); + } while (resultChar->NextRow()); + } + + return true; +} + +/** + * Generate WHERE guids list by deleted info in way preventing return too long where list for existed query string length limit. + * + * @param itr a reference to an deleted info list iterator, it updated in function for possible next function call if list to long + * @param itr_end a reference to an deleted info list iterator end() + * @return returns generated where list string in form: 'guid IN (gui1, guid2, ...)' + */ +std::string ChatHandler::GenerateDeletedCharacterGUIDsWhereStr(DeletedInfoList::const_iterator& itr, DeletedInfoList::const_iterator const& itr_end) { + std::ostringstream wherestr; + wherestr << "guid IN ('"; + for(; itr != itr_end; ++itr) + { + wherestr << itr->lowguid; + + if (wherestr.str().size() > MAX_QUERY_LEN - 50) // near to max query + { + ++itr; + break; + } + + DeletedInfoList::const_iterator itr2 = itr; + if(++itr2 != itr_end) + wherestr << "','"; + } + wherestr << "')"; + return wherestr.str(); +} + +/** + * Shows all deleted characters which matches the given search string, expected non empty list + * + * @see ChatHandler::HandleCharacterDeletedListCommand + * @see ChatHandler::HandleCharacterDeletedRestoreCommand + * @see ChatHandler::HandleCharacterDeletedDeleteCommand + * @see ChatHandler::DeletedInfoList + * + * @param foundList contains a list with all found deleted characters + */ +void ChatHandler::HandleCharacterDeletedListHelper(DeletedInfoList const& foundList) +{ + if (!m_session) + { + SendSysMessage(LANG_CHARACTER_DELETED_LIST_BAR); + SendSysMessage(LANG_CHARACTER_DELETED_LIST_HEADER); + SendSysMessage(LANG_CHARACTER_DELETED_LIST_BAR); + } + + for (DeletedInfoList::const_iterator itr = foundList.begin(); itr != foundList.end(); ++itr) + { + std::string dateStr = TimeToTimestampStr(itr->deleteDate); + + if (!m_session) + PSendSysMessage(LANG_CHARACTER_DELETED_LIST_LINE_CONSOLE, + itr->lowguid, itr->name.c_str(), itr->accountName.empty() ? "<Not existed>" : itr->accountName.c_str(), + itr->accountId, dateStr.c_str()); + else + PSendSysMessage(LANG_CHARACTER_DELETED_LIST_LINE_CHAT, + itr->lowguid, itr->name.c_str(), itr->accountName.empty() ? "<Not existed>" : itr->accountName.c_str(), + itr->accountId, dateStr.c_str()); + } + + if (!m_session) + SendSysMessage(LANG_CHARACTER_DELETED_LIST_BAR); +} + +/** + * Handles the '.character deleted list' command, which shows all deleted characters which matches the given search string + * + * @see ChatHandler::HandleCharacterDeletedListHelper + * @see ChatHandler::HandleCharacterDeletedRestoreCommand + * @see ChatHandler::HandleCharacterDeletedDeleteCommand + * @see ChatHandler::DeletedInfoList + * + * @param args the search string which either contains a player GUID or a part fo the character-name + */ +bool ChatHandler::HandleCharacterDeletedListCommand(const char* args) +{ + DeletedInfoList foundList; + if (!GetDeletedCharacterInfoList(foundList, args)) + return false; + + // if no characters have been found, output a warning + if (foundList.empty()) + { + SendSysMessage(LANG_CHARACTER_DELETED_LIST_EMPTY); + return false; + } + + HandleCharacterDeletedListHelper(foundList); + return true; +} + +/** + * Restore a previously deleted character + * + * @see ChatHandler::HandleCharacterDeletedListHelper + * @see ChatHandler::HandleCharacterDeletedRestoreCommand + * @see ChatHandler::HandleCharacterDeletedDeleteCommand + * @see ChatHandler::DeletedInfoList + * + * @param delInfo the informations about the character which will be restored + */ +void ChatHandler::HandleCharacterDeletedRestoreHelper(DeletedInfo const& delInfo) +{ + if (delInfo.accountName.empty()) // account not exist + { + PSendSysMessage(LANG_CHARACTER_DELETED_SKIP_ACCOUNT, delInfo.name.c_str(), delInfo.lowguid, delInfo.accountId); + return; + } + + // check character count + uint32 charcount = accmgr.GetCharactersCount(delInfo.accountId); + if (charcount >= 10) + { + PSendSysMessage(LANG_CHARACTER_DELETED_SKIP_FULL, delInfo.name.c_str(), delInfo.lowguid, delInfo.accountId); + return; + } + + if (objmgr.GetPlayerGUIDByName(delInfo.name)) + { + PSendSysMessage(LANG_CHARACTER_DELETED_SKIP_NAME, delInfo.name.c_str(), delInfo.lowguid, delInfo.accountId); + return; + } + + CharacterDatabase.PExecute("UPDATE characters SET name='%s', account='%u', deleteDate=NULL, deleteInfos_Name=NULL, deleteInfos_Account=NULL WHERE deleteDate IS NOT NULL AND guid = %u", + delInfo.name.c_str(), delInfo.accountId, delInfo.lowguid); +} + +/** + * Handles the '.character deleted restore' command, which restores all deleted characters which matches the given search string + * + * The command automatically calls '.character deleted list' command with the search string to show all restored characters. + * + * @see ChatHandler::HandleCharacterDeletedRestoreHelper + * @see ChatHandler::HandleCharacterDeletedListCommand + * @see ChatHandler::HandleCharacterDeletedDeleteCommand + * + * @param args the search string which either contains a player GUID or a part of the character-name + */ +bool ChatHandler::HandleCharacterDeletedRestoreCommand(const char* args) +{ + // It is required to submit at least one argument + if (!*args) + return false; + + std::string searchString; + std::string newCharName; + uint32 newAccount = 0; + + // GCC by some strange reason fail build code without temporary variable + std::istringstream params(args); + params >> searchString >> newCharName >> newAccount; + + DeletedInfoList foundList; + if (!GetDeletedCharacterInfoList(foundList, searchString)) + return false; + + if (foundList.empty()) + { + SendSysMessage(LANG_CHARACTER_DELETED_LIST_EMPTY); + return false; + } + + SendSysMessage(LANG_CHARACTER_DELETED_RESTORE); + HandleCharacterDeletedListHelper(foundList); + + if (newCharName.empty()) + { + // Drop not existed account cases + for (DeletedInfoList::iterator itr = foundList.begin(); itr != foundList.end(); ++itr) + HandleCharacterDeletedRestoreHelper(*itr); + } + else if (foundList.size() == 1 && normalizePlayerName(newCharName)) + { + DeletedInfo delInfo = foundList.front(); + + // update name + delInfo.name = newCharName; + + // if new account provided update deleted info + if (newAccount && newAccount != delInfo.accountId) + { + delInfo.accountId = newAccount; + accmgr.GetName(newAccount, delInfo.accountName); + } + + HandleCharacterDeletedRestoreHelper(delInfo); + } + else + SendSysMessage(LANG_CHARACTER_DELETED_ERR_RENAME); + + return true; +} + +/** + * Handles the '.character deleted delete' command, which completely deletes all deleted characters which matches the given search string + * + * @see Player::GetDeletedCharacterGUIDs + * @see Player::DeleteFromDB + * @see ChatHandler::HandleCharacterDeletedListCommand + * @see ChatHandler::HandleCharacterDeletedRestoreCommand + * + * @param args the search string which either contains a player GUID or a part fo the character-name + */ +bool ChatHandler::HandleCharacterDeletedDeleteCommand(const char* args) +{ + // It is required to submit at least one argument + if (!*args) + return false; + + DeletedInfoList foundList; + if (!GetDeletedCharacterInfoList(foundList, args)) + return false; + + if (foundList.empty()) + { + SendSysMessage(LANG_CHARACTER_DELETED_LIST_EMPTY); + return false; + } + + SendSysMessage(LANG_CHARACTER_DELETED_DELETE); + HandleCharacterDeletedListHelper(foundList); + + // Call the appropriate function to delete them (current account for deleted characters is 0) + for(DeletedInfoList::const_iterator itr = foundList.begin(); itr != foundList.end(); ++itr) + Player::DeleteFromDB(itr->lowguid, 0, false, true); + + return true; +} + +/** + * Handles the '.character deleted old' command, which completely deletes all deleted characters deleted with some days ago + * + * @see Player::DeleteOldCharacters + * @see Player::DeleteFromDB + * @see ChatHandler::HandleCharacterDeletedDeleteCommand + * @see ChatHandler::HandleCharacterDeletedListCommand + * @see ChatHandler::HandleCharacterDeletedRestoreCommand + * + * @param args the search string which either contains a player GUID or a part fo the character-name + */ +bool ChatHandler::HandleCharacterDeletedOldCommand(const char* args) +{ + int32 keepDays = sWorld.getConfig(CONFIG_CHARDELETE_KEEP_DAYS); + + char* px = strtok((char*)args, " "); + if (px) + { + if (!isNumeric(px)) + return false; + + keepDays = atoi(px); + if (keepDays < 0) + return false; + } + // config option value 0 -> disabled and can't be used + else if (keepDays <= 0) + return false; + + Player::DeleteOldCharacters((uint32)keepDays); + return true; +} + +bool ChatHandler::HandleCharacterEraseCommand(const char* args){ if(!*args) return false; @@ -202,7 +516,7 @@ bool ChatHandler::HandleCharacterDeleteCommand(const char* args) std::string account_name; accmgr.GetName (account_id,account_name); - Player::DeleteFromDB(character_guid, account_id, true); + Player::DeleteFromDB(character_guid, account_id, true, true); PSendSysMessage(LANG_CHARACTER_DELETED,character_name.c_str(),GUID_LOPART(character_guid),account_name.c_str(), account_id); return true; } diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 0f93bef4c06..2751212fae4 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -2087,6 +2087,33 @@ Ra.Port = 3443 Ra.MinLevel = 3 Ra.Secure = 1 +################################################################################################################### +# CharDelete.Method +# Character deletion behavior +# Default: 0 - Completely remove the character from the database +# 1 - Unlinking, the character gets unlinked from the account, +# the name gets freed up and appears as deleted ingame +# +# CharDelete.MinLevel +# Character gets deleted by CharDelete.Method=0 when the character +# hasn't the specified level yet. +# Default: 0 - For all characters the specified mode will be used +# 1+ - Only for players which have reached the specified level +# will be deleted by the specified mode. +# the rest will be deleted by CharDelete.Method=0 +# +# CharDelete.KeepDays +# Define the amount of days for which the characters are kept in the database before +# they will be removed +# Default: 30 +# 0 - Don't delete any characters, they stay in the database forever. +# +################################################################################################################### + +CharDelete.Method = 0 +CharDelete.MinLevel = 0 +CharDelete.KeepDays = 30 + ############################################################################### # CUSTOM SERVER OPTIONS # |