[9767] Add the posibility to keep deleted characters in the database for a while and also add related commands.

Added commands:
* .character deleted list [$guid|$name]
* .character deleted restore $guid|$name [$newname]
* .character deleted delete $guid|$name
* .character deleted old [$keepdays]

Command .character delete renamed to .character erase

Signed-off-by: VladimirMangos <vladimir@getmangos.com>
This commit is contained in:
DasBlub 2010-04-19 22:00:55 +04:00 committed by VladimirMangos
parent c9ac685f04
commit 492ce567d2
22 changed files with 701 additions and 151 deletions

View file

@ -21,7 +21,7 @@
DROP TABLE IF EXISTS `character_db_version`; DROP TABLE IF EXISTS `character_db_version`;
CREATE TABLE `character_db_version` ( CREATE TABLE `character_db_version` (
`required_9751_01_characters` bit(1) default NULL `required_9767_03_characters_characters` bit(1) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Last applied sql update to DB'; ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Last applied sql update to DB';
-- --
@ -256,6 +256,10 @@ CREATE TABLE `characters` (
`ammoId` int(10) UNSIGNED NOT NULL default '0', `ammoId` int(10) UNSIGNED NOT NULL default '0',
`knownTitles` longtext, `knownTitles` longtext,
`actionBars` tinyint(3) UNSIGNED NOT NULL default '0', `actionBars` tinyint(3) UNSIGNED NOT NULL default '0',
`deleteInfos_Account` int(11) UNSIGNED default,
`deleteInfos_Name` varchar(12) default NULL,
`deleteDate` bigint(20) default NULL,
PRIMARY KEY (`guid`), PRIMARY KEY (`guid`),
KEY `idx_account` (`account`), KEY `idx_account` (`account`),
KEY `idx_online` (`online`), KEY `idx_online` (`online`),

View file

@ -24,7 +24,7 @@ CREATE TABLE `db_version` (
`version` varchar(120) default NULL, `version` varchar(120) default NULL,
`creature_ai_version` varchar(120) default NULL, `creature_ai_version` varchar(120) default NULL,
`cache_id` int(10) default '0', `cache_id` int(10) default '0',
`required_9766_01_mangos_spell_proc_event` bit(1) default NULL `required_9767_02_mangos_command` bit(1) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Used DB version notes'; ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Used DB version notes';
-- --
@ -451,6 +451,7 @@ CREATE TABLE `battleground_template` (
LOCK TABLES `battleground_template` WRITE; LOCK TABLES `battleground_template` WRITE;
/*!40000 ALTER TABLE `battleground_template` DISABLE KEYS */; /*!40000 ALTER TABLE `battleground_template` DISABLE KEYS */;
INSERT INTO `battleground_template` VALUES
(1,40,40,611,2.72532,610,2.27452), (1,40,40,611,2.72532,610,2.27452),
(2,10,10,769,3.14159,770,3.14159), (2,10,10,769,3.14159,770,3.14159),
(3,15,15,890,3.40156,889,0.263892), (3,15,15,890,3.40156,889,0.263892),
@ -535,7 +536,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 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.'), ('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 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 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 erase',4,'Syntax: .character erase $name\r\n\r\nDelete character $name. Character finally deleted in case any deleting options.'),
('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 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 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.'), ('character reputation',2,'Syntax: .character reputation [$player_name]\r\n\r\nShow reputation information for selected player or player find by $player_name.'),
@ -3576,6 +3581,17 @@ INSERT INTO `mangos_string` VALUES
(1013,'|%15s| %20s | %15s |%4d| %9d |',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (1013,'|%15s| %20s | %15s |%4d| %9d |',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),
(1014,'No online players.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (1014,'No online players.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),
(1015,'Used not fully typed quit command, need type it fully (quit), or command used not in RA command line.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), (1015,'Used not fully typed quit command, need type it fully (quit), or command used not in RA command line.',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), (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), (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), (1102,'Message sent to %s: %s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL),

View file

@ -0,0 +1,15 @@
ALTER TABLE db_version CHANGE COLUMN required_9766_01_mangos_spell_proc_event required_9767_01_mangos_mangos_string bit;
DELETE FROM mangos_string WHERE entry IN (1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026);
INSERT INTO mangos_string VALUES
(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);

View file

@ -0,0 +1,9 @@
ALTER TABLE db_version CHANGE COLUMN required_9767_01_mangos_mangos_string required_9767_02_mangos_command bit;
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!');

View file

@ -0,0 +1,6 @@
ALTER TABLE character_db_version CHANGE COLUMN required_9751_01_characters required_9767_03_characters_characters bit;
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;

View file

@ -128,6 +128,9 @@ pkgdata_DATA = \
9761_01_mangos_mangos_string.sql \ 9761_01_mangos_mangos_string.sql \
9763_01_mangos_battleground_template.sql \ 9763_01_mangos_battleground_template.sql \
9766_01_mangos_spell_proc_event.sql \ 9766_01_mangos_spell_proc_event.sql \
9767_01_mangos_mangos_string.sql \
9767_02_mangos_command.sql \
9767_03_characters_characters.sql \
README README
## Additional files to include when running 'make dist' ## Additional files to include when running 'make dist'
@ -236,4 +239,7 @@ EXTRA_DIST = \
9761_01_mangos_mangos_string.sql \ 9761_01_mangos_mangos_string.sql \
9763_01_mangos_battleground_template.sql \ 9763_01_mangos_battleground_template.sql \
9766_01_mangos_spell_proc_event.sql \ 9766_01_mangos_spell_proc_event.sql \
9767_01_mangos_mangos_string.sql \
9767_02_mangos_command.sql \
9767_03_characters_characters.sql \
README README

View file

@ -62,6 +62,7 @@ AccountOpResult AccountMgr::DeleteAccount(uint32 accid)
return AOR_NAME_NOT_EXIST; // account doesn't exist return AOR_NAME_NOT_EXIST; // account doesn't exist
delete result; delete result;
// existed chaarcters list
result = CharacterDatabase.PQuery("SELECT guid FROM characters WHERE account='%d'",accid); result = CharacterDatabase.PQuery("SELECT guid FROM characters WHERE account='%d'",accid);
if (result) if (result)
{ {
@ -182,6 +183,21 @@ bool AccountMgr::GetName(uint32 acc_id, std::string &name)
return false; return false;
} }
uint32 AccountMgr::GetCharactersCount(uint32 acc_id)
{
// check character count
QueryResult *result = CharacterDatabase.PQuery("SELECT COUNT(guid) FROM characters WHERE account = '%d'", acc_id);
if (result)
{
Field *fields=result->Fetch();
uint32 charcount = fields[0].GetUInt32();
delete result;
return charcount;
}
else
return 0;
}
bool AccountMgr::CheckPassword(uint32 accid, std::string passwd) bool AccountMgr::CheckPassword(uint32 accid, std::string passwd)
{ {
std::string username; std::string username;

View file

@ -50,6 +50,7 @@ class AccountMgr
uint32 GetId(std::string username); uint32 GetId(std::string username);
AccountTypes GetSecurity(uint32 acc_id); AccountTypes GetSecurity(uint32 acc_id);
bool GetName(uint32 acc_id, std::string &name); bool GetName(uint32 acc_id, std::string &name);
uint32 GetCharactersCount(uint32 acc_id);
std::string CalculateShaPassHash(std::string& name, std::string& password); std::string CalculateShaPassHash(std::string& name, std::string& password);
static bool normalizeString(std::string& utf8str); static bool normalizeString(std::string& utf8str);

View file

@ -116,10 +116,20 @@ ChatCommand * ChatHandler::getCommandTable()
{ NULL, 0, false, NULL, "", NULL } { 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[] = static ChatCommand characterCommandTable[] =
{ {
{ "customize", SEC_GAMEMASTER, true, &ChatHandler::HandleCharacterCustomizeCommand, "", NULL }, { "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 }, { "level", SEC_ADMINISTRATOR, true, &ChatHandler::HandleCharacterLevelCommand, "", NULL },
{ "rename", SEC_GAMEMASTER, true, &ChatHandler::HandleCharacterRenameCommand, "", NULL }, { "rename", SEC_GAMEMASTER, true, &ChatHandler::HandleCharacterRenameCommand, "", NULL },
{ "reputation", SEC_GAMEMASTER, true, &ChatHandler::HandleCharacterReputationCommand, "", NULL }, { "reputation", SEC_GAMEMASTER, true, &ChatHandler::HandleCharacterReputationCommand, "", NULL },
@ -598,7 +608,7 @@ ChatCommand * ChatHandler::getCommandTable()
{ "modify", SEC_MODERATOR, false, NULL, "", modifyCommandTable }, { "modify", SEC_MODERATOR, false, NULL, "", modifyCommandTable },
{ "debug", SEC_MODERATOR, true, NULL, "", debugCommandTable }, { "debug", SEC_MODERATOR, true, NULL, "", debugCommandTable },
{ "tele", SEC_MODERATOR, true, NULL, "", teleCommandTable }, { "tele", SEC_MODERATOR, true, NULL, "", teleCommandTable },
{ "character", SEC_GAMEMASTER, false, NULL, "", characterCommandTable}, { "character", SEC_GAMEMASTER, true, NULL, "", characterCommandTable},
{ "event", SEC_GAMEMASTER, false, NULL, "", eventCommandTable }, { "event", SEC_GAMEMASTER, false, NULL, "", eventCommandTable },
{ "gobject", SEC_GAMEMASTER, false, NULL, "", gobjectCommandTable }, { "gobject", SEC_GAMEMASTER, false, NULL, "", gobjectCommandTable },
{ "honor", SEC_GAMEMASTER, false, NULL, "", honorCommandTable }, { "honor", SEC_GAMEMASTER, false, NULL, "", honorCommandTable },

View file

@ -125,7 +125,11 @@ class ChatHandler
bool HandleCastTargetCommand(const char *args); bool HandleCastTargetCommand(const char *args);
bool HandleCharacterCustomizeCommand(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 HandleCharacterLevelCommand(const char* args);
bool HandleCharacterRenameCommand(const char * args); bool HandleCharacterRenameCommand(const char * args);
bool HandleCharacterReputationCommand(const char* args); bool HandleCharacterReputationCommand(const char* args);
@ -546,6 +550,24 @@ class ChatHandler
void HandleLearnSkillRecipesHelper(Player* player,uint32 skill_id); void HandleLearnSkillRecipesHelper(Player* player,uint32 skill_id);
void ShowSpellListHelper(Player* target, SpellEntry const* spellInfo, LocaleConstant loc); void ShowSpellListHelper(Player* target, SpellEntry const* spellInfo, LocaleConstant loc);
/**
* 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& itr_end);
void HandleCharacterDeletedListHelper(DeletedInfoList const& foundList);
void HandleCharacterDeletedRestoreHelper(DeletedInfo const& delInfo);
void SetSentErrorMessage(bool val){ sentErrorMessage = val;}; void SetSentErrorMessage(bool val){ sentErrorMessage = val;};
private: private:
WorldSession * m_session; // != NULL for chat command call and NULL for CLI command WorldSession * m_session; // != NULL for chat command call and NULL for CLI command

View file

@ -781,7 +781,18 @@ enum MangosStrings
LANG_ACCOUNT_LIST_LINE = 1013, LANG_ACCOUNT_LIST_LINE = 1013,
LANG_ACCOUNT_LIST_EMPTY = 1014, LANG_ACCOUNT_LIST_EMPTY = 1014,
LANG_QUIT_WRONG_USE_ERROR = 1015, LANG_QUIT_WRONG_USE_ERROR = 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) // Level 3 (continue)
LANG_ACCOUNT_SETADDON = 1100, LANG_ACCOUNT_SETADDON = 1100,

View file

@ -5627,7 +5627,7 @@ bool ChatHandler::HandlePDumpWriteCommand(const char *args)
uint32 guid; uint32 guid;
// character name can't start from number // character name can't start from number
if (isNumeric(p2[0])) if (isNumeric(p2))
guid = atoi(p2); guid = atoi(p2);
else else
{ {

View file

@ -4037,8 +4037,31 @@ TrainerSpellState Player::GetTrainerSpellState(TrainerSpell const* trainer_spell
return TRAINER_SPELL_GREEN; 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_UINT32_CHARDELETE_METHOD);
uint32 charDelete_minLvl = sWorld.getConfig(CONFIG_UINT32_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 0
if (deleteFinally || Player::GetLevelFromDB(playerguid) < charDelete_minLvl)
charDelete_method = 0;
uint32 guid = GUID_LOPART(playerguid); uint32 guid = GUID_LOPART(playerguid);
// convert corpse to bones if exist (to prevent exiting Corpse in World without DB entry) // convert corpse to bones if exist (to prevent exiting Corpse in World without DB entry)
@ -4046,20 +4069,16 @@ void Player::DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmC
sObjectAccessor.ConvertCorpseForPlayer(playerguid); sObjectAccessor.ConvertCorpseForPlayer(playerguid);
// remove from guild // remove from guild
uint32 guildId = GetGuildIdFromDB(playerguid); if (uint32 guildId = GetGuildIdFromDB(playerguid))
if(guildId != 0) if (Guild* guild = sObjectMgr.GetGuildById(guildId))
{
Guild* guild = sObjectMgr.GetGuildById(guildId);
if(guild)
guild->DelMember(guid); guild->DelMember(guid);
}
// remove from arena teams // remove from arena teams
LeaveAllArenaTeams(playerguid); LeaveAllArenaTeams(playerguid);
// the player was uninvited already on logout so just remove from group // the player was uninvited already on logout so just remove from group
QueryResult *resultGroup = CharacterDatabase.PQuery("SELECT groupId FROM group_member WHERE memberGuid='%u'", guid); QueryResult *resultGroup = CharacterDatabase.PQuery("SELECT groupId FROM group_member WHERE memberGuid='%u'", guid);
if(resultGroup) if (resultGroup)
{ {
uint32 groupId = (*resultGroup)[0].GetUInt32(); uint32 groupId = (*resultGroup)[0].GetUInt32();
delete resultGroup; delete resultGroup;
@ -4070,139 +4089,191 @@ void Player::DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmC
// remove signs from petitions (also remove petitions if owner); // remove signs from petitions (also remove petitions if owner);
RemovePetitionsAndSigns(playerguid, 10); RemovePetitionsAndSigns(playerguid, 10);
// return back all mails with COD and Item 0 1 2 3 4 5 6 7 switch(charDelete_method)
QueryResult *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)
{ {
do // completely remove from the database
case 0:
{ {
Field *fields = resultMail->Fetch(); // return back all mails with COD and Item 0 1 2 3 4 5 6 7
QueryResult *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);
uint32 mail_id = fields[0].GetUInt32(); if (resultMail)
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) do
CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mail_id);
continue;
}
MailDraft draft(subject, body);
if (mailTemplateId)
draft = MailDraft(mailTemplateId, false); // items already included
if(has_items)
{
// data needs to be at first place for Item::LoadFromDB
// 0 1 2 3
QueryResult *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 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)
{ {
Field *fields2 = resultItems->Fetch(); if(has_items)
CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mail_id);
uint32 item_guidlow = fields2[2].GetUInt32(); continue;
uint32 item_template = fields2[3].GetUInt32();
ItemPrototype const* itemProto = ObjectMgr::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);
} }
while (resultItems->NextRow());
delete resultItems; MailDraft draft(subject, body);
if (mailTemplateId)
draft = MailDraft(mailTemplateId, false); // items already included
if (has_items)
{
// data needs to be at first place for Item::LoadFromDB
// 0 1 2 3
QueryResult *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
{
Field *fields2 = resultItems->Fetch();
uint32 item_guidlow = fields2[2].GetUInt32();
uint32 item_template = fields2[3].GetUInt32();
ItemPrototype const* itemProto = ObjectMgr::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);
}
while (resultItems->NextRow());
delete resultItems;
}
}
CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mail_id);
uint32 pl_account = sObjectMgr.GetPlayerAccountIdByGUID(MAKE_NEW_GUID(guid, 0, HIGHGUID_PLAYER));
draft.AddMoney(money).SendReturnToSender(pl_account, guid, sender);
} }
while (resultMail->NextRow());
delete resultMail;
} }
CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mail_id); // 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 *resultPets = CharacterDatabase.PQuery("SELECT id FROM character_pet WHERE owner = '%u'",guid);
uint32 pl_account = sObjectMgr.GetPlayerAccountIdByGUID(MAKE_NEW_GUID(guid, 0, HIGHGUID_PLAYER)); // 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());
delete resultPets;
}
draft.AddMoney(money).SendReturnToSender(pl_account, guid, sender); 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_glyphs 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 character_inventory WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_queststatus WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_queststatus_daily WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_queststatus_weekly WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_reputation WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_skills 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 character_talent WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_ticket WHERE guid = '%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'",guid);
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.CommitTransaction();
break;
} }
while (resultMail->NextRow()); // The character gets unlinked from the account, the name gets freed up and appears as deleted ingame
case 1:
delete resultMail; CharacterDatabase.PExecute("UPDATE characters SET deleteInfos_Name=name, deleteInfos_Account=account, deleteDate=%u, 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. if (updateRealmChars)
// Get guids of character's pets, will deleted in transaction sWorld.UpdateRealmCharCount(accountId);
QueryResult *resultPets = CharacterDatabase.PQuery("SELECT id FROM character_pet WHERE owner = '%u'",guid); }
// NOW we can finally clear other DB data related to character /**
CharacterDatabase.BeginTransaction(); * 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.
if (resultPets) *
* @see Player::DeleteFromDB
*/
void Player::DeleteOldCharacters()
{
uint32 keepDays = sWorld.getConfig(CONFIG_UINT32_CHARDELETE_KEEP_DAYS);
if (!keepDays)
return;
Player::DeleteOldCharacters(keepDays);
}
/**
* 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 *resultChars = CharacterDatabase.PQuery("SELECT guid, deleteInfos_Account FROM characters WHERE deleteDate IS NOT NULL AND deleteDate < %u", uint64(time(NULL) - time_t(keepDays * DAY)));
if (resultChars)
{ {
sLog.outString("Player::DeleteOldChars: Found %u character(s) to delete",resultChars->GetRowCount());
do do
{ {
Field *fields3 = resultPets->Fetch(); Field *charFields = resultChars->Fetch();
uint32 petguidlow = fields3[0].GetUInt32(); Player::DeleteFromDB(charFields[0].GetUInt64(), charFields[1].GetUInt32(), true, true);
Pet::DeleteFromDB(petguidlow); } while(resultChars->NextRow());
} while (resultPets->NextRow()); delete resultChars;
delete resultPets;
} }
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_glyphs 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 character_inventory WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_queststatus WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_queststatus_daily WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_queststatus_weekly WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_reputation WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_skills 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 character_talent WHERE guid = '%u'",guid);
CharacterDatabase.PExecute("DELETE FROM character_ticket WHERE guid = '%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'",guid);
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.CommitTransaction();
//loginDatabase.PExecute("UPDATE realmcharacters SET numchars = numchars - 1 WHERE acctid = %d AND realmid = %d", accountId, realmID);
if(updateRealmChars) sWorld.UpdateRealmCharCount(accountId);
} }
void Player::SetMovement(PlayerMovementType pType) void Player::SetMovement(PlayerMovementType pType)

View file

@ -1454,6 +1454,10 @@ class MANGOS_DLL_SPEC Player : public Unit
static void Customize(uint64 guid, uint8 gender, uint8 skin, uint8 face, uint8 hairStyle, uint8 hairColor, uint8 facialHair); 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 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_mailsUpdated; bool m_mailsUpdated;
void SendPetTameFailure(PetTameFailureReason reason); void SendPetTameFailure(PetTameFailureReason reason);
@ -1832,8 +1836,6 @@ class MANGOS_DLL_SPEC Player : public Unit
// overwrite Object::SendMessageToSetInRange // overwrite Object::SendMessageToSetInRange
void SendMessageToSetInRange(WorldPacket *data, float dist, bool self, bool own_team_only); void SendMessageToSetInRange(WorldPacket *data, float dist, bool self, bool own_team_only);
static void DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmChars = true);
Corpse *GetCorpse() const; Corpse *GetCorpse() const;
void SpawnCorpseBones(); void SpawnCorpseBones();
Corpse* CreateCorpse(); Corpse* CreateCorpse();

View file

@ -22,6 +22,7 @@
#include "Database/SQLStorage.h" #include "Database/SQLStorage.h"
#include "UpdateFields.h" #include "UpdateFields.h"
#include "ObjectMgr.h" #include "ObjectMgr.h"
#include "AccountMgr.h"
// Character Dump tables // Character Dump tables
struct DumpTable struct DumpTable
@ -394,19 +395,9 @@ DumpReturn PlayerDumpWriter::WriteDump(const std::string& file, uint32 guid)
DumpReturn PlayerDumpReader::LoadDump(const std::string& file, uint32 account, std::string name, uint32 guid) DumpReturn PlayerDumpReader::LoadDump(const std::string& file, uint32 account, std::string name, uint32 guid)
{ {
// check character count // check character count
{ uint32 charcount = sAccountMgr.GetCharactersCount(account);
QueryResult *result = CharacterDatabase.PQuery("SELECT COUNT(guid) FROM characters WHERE account = '%d'", account); if (charcount >= 10)
uint8 charcount = 0; return DUMP_TOO_MANY_CHARS;
if (result)
{
Field *fields=result->Fetch();
charcount = fields[0].GetUInt8();
delete result;
if (charcount >= 10)
return DUMP_TOO_MANY_CHARS;
}
}
FILE *fin = fopen(file.c_str(), "r"); FILE *fin = fopen(file.c_str(), "r");
if (!fin) if (!fin)

View file

@ -827,6 +827,11 @@ void World::LoadConfigSettings(bool reload)
m_MaxVisibleDistanceInFlight = MAX_VISIBILITY_DISTANCE - m_VisibleObjectGreyDistance; m_MaxVisibleDistanceInFlight = MAX_VISIBILITY_DISTANCE - m_VisibleObjectGreyDistance;
} }
///- Load the CharDelete related config options
setConfigMinMax(CONFIG_UINT32_CHARDELETE_METHOD, "CharDelete.Method", 0, 0, 1);
setConfigMinMax(CONFIG_UINT32_CHARDELETE_MIN_LEVEL, "CharDelete.MinLevel", 0, 0, getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL));
setConfigPos(CONFIG_UINT32_CHARDELETE_KEEP_DAYS, "CharDelete.KeepDays", 30);
///- Read the "Data" directory from the config file ///- Read the "Data" directory from the config file
std::string dataPath = sConfig.GetStringDefault("DataDir","./"); std::string dataPath = sConfig.GetStringDefault("DataDir","./");
if( dataPath.at(dataPath.length()-1)!='/' && dataPath.at(dataPath.length()-1)!='\\' ) if( dataPath.at(dataPath.length()-1)!='/' && dataPath.at(dataPath.length()-1)!='\\' )
@ -1237,6 +1242,7 @@ void World::SetInitialWorldSettings()
m_timers[WUPDATE_UPTIME].SetInterval(m_configUint32Values[CONFIG_UINT32_UPTIME_UPDATE]*MINUTE*IN_MILLISECONDS); m_timers[WUPDATE_UPTIME].SetInterval(m_configUint32Values[CONFIG_UINT32_UPTIME_UPDATE]*MINUTE*IN_MILLISECONDS);
//Update "uptime" table based on configuration entry in minutes. //Update "uptime" table based on configuration entry in minutes.
m_timers[WUPDATE_CORPSES].SetInterval(3*HOUR*IN_MILLISECONDS); m_timers[WUPDATE_CORPSES].SetInterval(3*HOUR*IN_MILLISECONDS);
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 //to set mailtimer to return mails every day between 4 and 5 am
//mailtimer is increased when updating auctions //mailtimer is increased when updating auctions
@ -1279,6 +1285,9 @@ void World::SetInitialWorldSettings()
uint32 nextGameEvent = sGameEventMgr.Initialize(); uint32 nextGameEvent = sGameEventMgr.Initialize();
m_timers[WUPDATE_EVENTS].SetInterval(nextGameEvent); //depend on next event m_timers[WUPDATE_EVENTS].SetInterval(nextGameEvent); //depend on next event
// Delete all characters which have been deleted X days before
Player::DeleteOldCharacters();
sLog.outString( "WORLD: World initialized" ); sLog.outString( "WORLD: World initialized" );
uint32 uStartInterval = getMSTimeDiff(uStartTime, getMSTime()); uint32 uStartInterval = getMSTimeDiff(uStartTime, getMSTime());
@ -1423,6 +1432,13 @@ void World::Update(uint32 diff)
sBattleGroundMgr.Update(diff); sBattleGroundMgr.Update(diff);
} }
///- Delete all characters which have been deleted X days before
if (m_timers[WUPDATE_DELETECHARS].Passed())
{
m_timers[WUPDATE_DELETECHARS].Reset();
Player::DeleteOldCharacters();
}
// execute callbacks from sql queries that were queued recently // execute callbacks from sql queries that were queued recently
UpdateResultQueue(); UpdateResultQueue();

View file

@ -77,7 +77,8 @@ enum WorldTimers
WUPDATE_UPTIME = 4, WUPDATE_UPTIME = 4,
WUPDATE_CORPSES = 5, WUPDATE_CORPSES = 5,
WUPDATE_EVENTS = 6, WUPDATE_EVENTS = 6,
WUPDATE_COUNT = 7 WUPDATE_DELETECHARS = 7,
WUPDATE_COUNT = 8
}; };
/// Configuration elements /// Configuration elements
@ -177,6 +178,9 @@ enum eConfigUInt32Values
CONFIG_UINT32_TIMERBAR_FIRE_GMLEVEL, CONFIG_UINT32_TIMERBAR_FIRE_GMLEVEL,
CONFIG_UINT32_TIMERBAR_FIRE_MAX, CONFIG_UINT32_TIMERBAR_FIRE_MAX,
CONFIG_UINT32_MIN_LEVEL_STAT_SAVE, CONFIG_UINT32_MIN_LEVEL_STAT_SAVE,
CONFIG_UINT32_CHARDELETE_KEEP_DAYS,
CONFIG_UINT32_CHARDELETE_METHOD,
CONFIG_UINT32_CHARDELETE_MIN_LEVEL,
CONFIG_UINT32_VALUE_COUNT CONFIG_UINT32_VALUE_COUNT
}; };

View file

@ -114,7 +114,322 @@ bool ChatHandler::HandleAccountDeleteCommand(const char* args)
return true; 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* resultChar;
if (!searchString.empty())
{
// search by GUID
if (isNumeric(searchString))
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
sAccountMgr.GetName (info.accountId, info.accountName);
info.deleteDate = time_t(fields[3].GetUInt64());
foundList.push_back(info);
} while (resultChar->NextRow());
delete resultChar;
}
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 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 = sAccountMgr.GetCharactersCount(delInfo.accountId);
if (charcount >= 10)
{
PSendSysMessage(LANG_CHARACTER_DELETED_SKIP_FULL, delInfo.name.c_str(), delInfo.lowguid, delInfo.accountId);
return;
}
if (sObjectMgr.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;
std::stringstream(args) >> 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;
sAccountMgr.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_UINT32_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) if(!*args)
return false; return false;
@ -153,7 +468,7 @@ bool ChatHandler::HandleCharacterDeleteCommand(const char* args)
std::string account_name; std::string account_name;
sAccountMgr.GetName (account_id,account_name); sAccountMgr.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); PSendSysMessage(LANG_CHARACTER_DELETED,character_name.c_str(),GUID_LOPART(character_guid),account_name.c_str(), account_id);
return true; return true;
} }

View file

@ -1451,3 +1451,29 @@ SOAP.Enabled = 0
SOAP.IP = 127.0.0.1 SOAP.IP = 127.0.0.1
SOAP.Port = 7878 SOAP.Port = 7878
###################################################################################################################
# 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.Mode=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.Mode=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

View file

@ -191,6 +191,15 @@ inline bool isNumericOrSpace(wchar_t wchar)
return isNumeric(wchar) || wchar == L' '; return isNumeric(wchar) || wchar == L' ';
} }
inline bool isNumeric(char const* str)
{
for(char const* c = str; *c; ++c)
if (!isNumeric(*c))
return false;
return true;
}
inline bool isNumeric(std::string const& str) inline bool isNumeric(std::string const& str)
{ {
for(std::string::const_iterator itr = str.begin(); itr != str.end(); ++itr) for(std::string::const_iterator itr = str.begin(); itr != str.end(); ++itr)

View file

@ -1,4 +1,4 @@
#ifndef __REVISION_NR_H__ #ifndef __REVISION_NR_H__
#define __REVISION_NR_H__ #define __REVISION_NR_H__
#define REVISION_NR "9766" #define REVISION_NR "9767"
#endif // __REVISION_NR_H__ #endif // __REVISION_NR_H__

View file

@ -1,6 +1,6 @@
#ifndef __REVISION_SQL_H__ #ifndef __REVISION_SQL_H__
#define __REVISION_SQL_H__ #define __REVISION_SQL_H__
#define REVISION_DB_CHARACTERS "required_9751_01_characters" #define REVISION_DB_CHARACTERS "required_9767_03_characters_characters"
#define REVISION_DB_MANGOS "required_9766_01_mangos_spell_proc_event" #define REVISION_DB_MANGOS "required_9767_02_mangos_command"
#define REVISION_DB_REALMD "required_9748_01_realmd_realmlist" #define REVISION_DB_REALMD "required_9748_01_realmd_realmlist"
#endif // __REVISION_SQL_H__ #endif // __REVISION_SQL_H__