From 2e0c892b07815a8082ea676ab2bb8134f9281771 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sat, 18 Jul 2020 21:07:22 -0500 Subject: [PATCH] Add cli character:copy-character and #copycharacter --- common/database.cpp | 138 +++++++++++++++++++++++++ common/database.h | 39 +++++-- common/string_util.cpp | 19 +++- common/string_util.h | 9 +- world/world_server_command_handler.cpp | 48 ++++++++- world/world_server_command_handler.h | 1 + zone/command.cpp | 33 ++++++ zone/command.h | 1 + 8 files changed, 270 insertions(+), 18 deletions(-) diff --git a/common/database.cpp b/common/database.cpp index dbf4cabea..01520d5a3 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -2309,3 +2309,141 @@ int Database::GetInstanceID(uint32 char_id, uint32 zone_id) { return 0; } +/** + * @param source_character_name + * @param destination_character_name + * @param destination_account_name + * @return + */ +bool Database::CopyCharacter( + std::string source_character_name, + std::string destination_character_name, + std::string destination_account_name +) +{ + auto results = QueryDatabase( + fmt::format( + "SELECT id FROM character_data WHERE name = '{}' and deleted_at is NULL LIMIT 1", + source_character_name + ) + ); + + if (results.RowCount() == 0) { + LogError("No character found with name [{}]", source_character_name); + } + + auto row = results.begin(); + std::string source_character_id = row[0]; + + results = QueryDatabase( + fmt::format( + "SELECT id FROM account WHERE name = '{}' LIMIT 1", + destination_account_name + ) + ); + + if (results.RowCount() == 0) { + LogError("No account found with name [{}]", destination_account_name); + } + + row = results.begin(); + std::string source_account_id = row[0]; + + /** + * Fresh ID + */ + results = QueryDatabase("SELECT (MAX(id) + 1) as new_id from character_data"); + row = results.begin(); + std::string new_character_id = row[0]; + + TransactionBegin(); + for (const auto &iter : DatabaseSchema::GetCharacterTables()) { + std::string table_name = iter.first; + std::string character_id_column_name = iter.second; + + /** + * Columns + */ + results = QueryDatabase(fmt::format("SHOW COLUMNS FROM {}", table_name)); + std::vector columns = {}; + int column_count = 0; + for (row = results.begin(); row != results.end(); ++row) { + columns.emplace_back(row[0]); + column_count++; + } + + results = QueryDatabase( + fmt::format( + "SELECT {} FROM {} WHERE {} = {}", + implode(",", wrap(columns, "`")), + table_name, + character_id_column_name, + source_character_id + ) + ); + + std::vector> new_rows; + for (row = results.begin(); row != results.end(); ++row) { + std::vector new_values = {}; + for (int column_index = 0; column_index < column_count; column_index++) { + std::string column = columns[column_index]; + std::string value = row[column_index] ? row[column_index] : "null"; + + if (column == character_id_column_name) { + value = new_character_id; + } + + if (column == "name" && table_name == "character_data") { + value = destination_character_name; + } + + if (column == "account_id" && table_name == "character_data") { + value = source_account_id; + } + + new_values.emplace_back(value); + } + + new_rows.emplace_back(new_values); + } + + std::string insert_values; + std::vector insert_rows; + + for (auto &r: new_rows) { + std::string insert_row = "(" + implode(",", wrap(r, "'")) + ")"; + insert_rows.emplace_back(insert_row); + } + + if (!insert_rows.empty()) { + QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + table_name, + character_id_column_name, + new_character_id + ) + ); + + auto insert = QueryDatabase( + fmt::format( + "INSERT INTO {} ({}) VALUES {}", + table_name, + implode(",", wrap(columns, "`")), + implode(",", insert_rows) + ) + ); + + if (!insert.ErrorMessage().empty()) { + TransactionRollback(); + return false; + break; + } + } + } + + TransactionCommit(); + + return true; +} + diff --git a/common/database.h b/common/database.h index 1d55484d5..74566f4fd 100644 --- a/common/database.h +++ b/common/database.h @@ -105,16 +105,35 @@ public: /* Character Creation */ - bool AddToNameFilter(const char* name); - bool CreateCharacter(uint32 account_id, char* name, uint16 gender, uint16 race, uint16 class_, uint8 str, uint8 sta, uint8 cha, uint8 dex, uint8 int_, uint8 agi, uint8 wis, uint8 face); - bool DeleteCharacter(char* character_name); - bool MoveCharacterToZone(const char* charname, uint32 zone_id); - bool MoveCharacterToZone(uint32 character_id, uint32 zone_id); - bool ReserveName(uint32 account_id, char* name); - bool SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); - bool SetHackerFlag(const char* accountname, const char* charactername, const char* hacked); - bool SetMQDetectionFlag(const char* accountname, const char* charactername, const char* hacked, const char* zone); - bool UpdateName(const char* oldname, const char* newname); + bool AddToNameFilter(const char *name); + bool CreateCharacter( + uint32 account_id, + char *name, + uint16 gender, + uint16 race, + uint16 class_, + uint8 str, + uint8 sta, + uint8 cha, + uint8 dex, + uint8 int_, + uint8 agi, + uint8 wis, + uint8 face + ); + bool DeleteCharacter(char *character_name); + bool MoveCharacterToZone(const char *charname, uint32 zone_id); + bool MoveCharacterToZone(uint32 character_id, uint32 zone_id); + bool ReserveName(uint32 account_id, char *name); + bool SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct *pp); + bool SetHackerFlag(const char *accountname, const char *charactername, const char *hacked); + bool SetMQDetectionFlag(const char *accountname, const char *charactername, const char *hacked, const char *zone); + bool UpdateName(const char *oldname, const char *newname); + bool CopyCharacter( + std::string source_character_name, + std::string destination_character_name, + std::string destination_account_name + ); /* General Information Queries */ diff --git a/common/string_util.cpp b/common/string_util.cpp index 6a4555ccf..680c2822b 100644 --- a/common/string_util.cpp +++ b/common/string_util.cpp @@ -35,7 +35,7 @@ #define va_copy(d,s) ((d) = (s)) #endif -// original source: +// original source: // https://github.com/facebook/folly/blob/master/folly/String.cpp // const std::string vStringFormat(const char* format, va_list args) @@ -144,6 +144,23 @@ std::string implode(std::string glue, std::vector src) return final_output; } +std::vector wrap(std::vector &src, std::string character) +{ + std::vector new_vector; + new_vector.reserve(src.size()); + + for (auto &e: src) { + if (e == "null") { + new_vector.emplace_back(e); + continue; + } + + new_vector.emplace_back(character + e + character); + } + + return new_vector; +} + std::string EscapeString(const std::string &s) { std::string ret; diff --git a/common/string_util.h b/common/string_util.h index 3fb5e3ec9..0d602e395 100644 --- a/common/string_util.h +++ b/common/string_util.h @@ -42,6 +42,7 @@ const std::string ucfirst(std::string s); std::vector split(std::string str_to_split, char delimiter); const std::string StringFormat(const char* format, ...); const std::string vStringFormat(const char* format, va_list args); +std::vector wrap(std::vector &src, std::string character); std::string implode(std::string glue, std::vector src); std::string convert2digit(int n, std::string suffix); std::string numberToWords(unsigned long long int n); @@ -98,14 +99,14 @@ std::string implode(const std::string &glue, const std::pair &encaps } std::ostringstream oss; - + for (const T &src_iter : src) { oss << encapsulation.first << src_iter << encapsulation.second << glue; } std::string output(oss.str()); output.resize(output.size() - glue.size()); - + return output; } @@ -121,7 +122,7 @@ std::vector join_pair(const std::string &glue, const std::pair &src_iter : src) { output.push_back( - + fmt::format( "{}{}{}{}{}{}{}", encapsulation.first, @@ -151,7 +152,7 @@ std::vector join_tuple(const std::string &glue, const std::pair &src_iter : src) { output.push_back( - + fmt::format( "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", encapsulation.first, diff --git a/world/world_server_command_handler.cpp b/world/world_server_command_handler.cpp index b588577a4..0d1eecafc 100644 --- a/world/world_server_command_handler.cpp +++ b/world/world_server_command_handler.cpp @@ -54,6 +54,7 @@ namespace WorldserverCommandHandler { * Register commands */ function_map["world:version"] = &WorldserverCommandHandler::Version; + function_map["character:copy-character"] = &WorldserverCommandHandler::CopyCharacter; function_map["database:version"] = &WorldserverCommandHandler::DatabaseVersion; function_map["database:set-account-status"] = &WorldserverCommandHandler::DatabaseSetAccountStatus; function_map["database:schema"] = &WorldserverCommandHandler::DatabaseGetSchema; @@ -240,12 +241,12 @@ namespace WorldserverCommandHandler { "--compress" }; - - if (argc < 3 || cmd[{"-h", "--help"}]) { - EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); + if (cmd[{"-h", "--help"}]) { return; } + EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); + auto database_dump_service = new DatabaseDumpService(); bool dump_all = cmd[{"-a", "--all"}]; @@ -453,4 +454,45 @@ namespace WorldserverCommandHandler { } } + /** + * @param argc + * @param argv + * @param cmd + * @param description + */ + void CopyCharacter(int argc, char **argv, argh::parser &cmd, std::string &description) + { + description = "Copies a character into a destination account"; + + std::vector arguments = { + "source_character_name", + "destination_character_name", + "destination_account_name" + }; + std::vector options = { }; + + if (cmd[{"-h", "--help"}]) { + return; + } + + EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); + + std::string source_character_name = cmd(2).str(); + std::string destination_character_name = cmd(3).str(); + std::string destination_account_name = cmd(4).str(); + + LogInfo( + "Attempting to copy character [{}] to [{}] via account [{}]", + source_character_name, + destination_character_name, + destination_account_name + ); + + database.CopyCharacter( + source_character_name, + destination_character_name, + destination_account_name + ); + } + } diff --git a/world/world_server_command_handler.h b/world/world_server_command_handler.h index 251e1d048..2d7c59f8e 100644 --- a/world/world_server_command_handler.h +++ b/world/world_server_command_handler.h @@ -27,6 +27,7 @@ namespace WorldserverCommandHandler { void CommandHandler(int argc, char **argv); void Version(int argc, char **argv, argh::parser &cmd, std::string &description); + void CopyCharacter(int argc, char **argv, argh::parser &cmd, std::string &description); void DatabaseVersion(int argc, char **argv, argh::parser &cmd, std::string &description); void DatabaseSetAccountStatus(int argc, char **argv, argh::parser &cmd, std::string &description); void DatabaseGetSchema(int argc, char **argv, argh::parser &cmd, std::string &description); diff --git a/zone/command.cpp b/zone/command.cpp index 8fa63c15e..5a01f6a92 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -177,6 +177,7 @@ int command_init(void) command_add("castspell", "[spellid] - Cast a spell", 50, command_castspell) || command_add("chat", "[channel num] [message] - Send a channel message to all zones", 200, command_chat) || command_add("checklos", "- Check for line of sight to your target", 50, command_checklos) || + command_add("copycharacter", "[source_char_name] [dest_char_name] [dest_account_name] Copies character to destination account", 250, command_copycharacter) || command_add("corpse", "- Manipulate corpses, use with no arguments for help", 50, command_corpse) || command_add("corpsefix", "Attempts to bring corpses from underneath the ground within close proximity of the player", 0, command_corpsefix) || command_add("crashtest", "- Crash the zoneserver", 255, command_crashtest) || @@ -4578,6 +4579,38 @@ void command_zonelock(Client *c, const Seperator *sep) safe_delete(pack); } +void command_copycharacter(Client *c, const Seperator *sep) +{ + if (sep->argnum < 3) { + c->Message( + Chat::White, + "Usage: [source_character_name] [destination_character_name] [destination_account_name]" + ); + return; + } + + std::string source_character_name = sep->arg[1]; + std::string destination_character_name = sep->arg[2]; + std::string destination_account_name = sep->arg[3]; + + bool result = database.CopyCharacter( + source_character_name, + destination_character_name, + destination_account_name + ); + + c->Message( + Chat::Yellow, + fmt::format( + "Character Copy [{}] to [{}] via account [{}] [{}]", + source_character_name, + destination_character_name, + destination_account_name, + result ? "Success" : "Failed" + ).c_str() + ); +} + void command_corpse(Client *c, const Seperator *sep) { Mob *target=c->GetTarget(); diff --git a/zone/command.h b/zone/command.h index d2cd14467..f2f22fbcf 100644 --- a/zone/command.h +++ b/zone/command.h @@ -71,6 +71,7 @@ void command_chat(Client *c, const Seperator *sep); void command_checklos(Client *c, const Seperator *sep); void command_clearinvsnapshots(Client *c, const Seperator *sep); void command_connectworldserver(Client *c, const Seperator *sep); +void command_copycharacter(Client *c, const Seperator *sep); void command_corpse(Client *c, const Seperator *sep); void command_corpsefix(Client *c, const Seperator *sep); void command_crashtest(Client *c, const Seperator *sep);