aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/game/Accounts/AccountMgr.cpp14
-rw-r--r--src/server/game/Accounts/AccountMgr.h1
-rw-r--r--src/server/game/Chat/Chat.cpp14
-rw-r--r--src/server/game/Chat/Chat.h22
-rw-r--r--src/server/game/Chat/Commands/Level3.cpp2
-rw-r--r--src/server/game/Entities/Player/Player.cpp310
-rw-r--r--src/server/game/Entities/Player/Player.h13
-rw-r--r--src/server/game/Miscellaneous/Language.h13
-rw-r--r--src/server/game/Tools/PlayerDump.cpp17
-rw-r--r--src/server/game/World/World.cpp16
-rw-r--r--src/server/game/World/World.h6
-rw-r--r--src/server/shared/Utilities/Util.h9
-rw-r--r--src/server/worldserver/CommandLine/CliRunnable.cpp318
-rw-r--r--src/server/worldserver/worldserver.conf.dist27
14 files changed, 641 insertions, 141 deletions
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
#