diff --git a/.gitignore b/.gitignore index 287c07412..b8ec6881f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,19 @@ perl/ submodules/* cmake-build-debug/ -.nfs.* \ No newline at end of file +.nfs.* + +# Visual Studio and CMAKE Generated Files +/.vs/ +*.vcxproj +*.vcxproj.filters +*.vcxproj.user +*.cmake +*.ilk +*.pdb +*.sln +*.dir/ +libs/ +bin/ +/Win32 +/client_files/**/CMakeFiles/ diff --git a/README.md b/README.md index f3b829732..31f88be38 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # EQEmulator Core Server -|Travis CI (Linux)|Appveyor w/ Bots (Windows) |Appveyor w/o Bots (Windows) | +|Travis CI (Linux)|Appveyor (Windows x86) |Appveyor (Windows x64) | |:---:|:---:|:---:| -|[![Linux CI](https://travis-ci.org/EQEmu/Server.svg?branch=master)](https://travis-ci.org/EQEmu/Server) |[![Build status](https://ci.appveyor.com/api/projects/status/scr25kmntx36c1ub/branch/master?svg=true)](https://ci.appveyor.com/project/KimLS/server-87crp/branch/master) |[![Build status](https://ci.appveyor.com/api/projects/status/mdwbr4o9l6mxqofj/branch/master?svg=true)](https://ci.appveyor.com/project/KimLS/server-w0pq2/branch/master) | +|[![Linux CI](https://travis-ci.org/EQEmu/Server.svg?branch=master)](https://travis-ci.org/EQEmu/Server) |[![Build status](https://ci.appveyor.com/api/projects/status/v3utuu0dttm2cqd0?svg=true)](https://ci.appveyor.com/project/KimLS/server) |[![Build status](https://ci.appveyor.com/api/projects/status/scr25kmntx36c1ub?svg=true)](https://ci.appveyor.com/project/KimLS/server-87crp) | *** diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index a959d9093..e312c202d 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -9,6 +9,7 @@ SET(common_sources crash.cpp crc16.cpp crc32.cpp + database/database_dump_service.cpp database.cpp database_conversions.cpp database_instances.cpp @@ -31,6 +32,7 @@ SET(common_sources event_sub.cpp extprofile.cpp faction.cpp + file_util.cpp guild_base.cpp guilds.cpp inventory_profile.cpp @@ -120,6 +122,7 @@ SET(common_headers cli/argh.h cli/eqemu_command_handler.h cli/terminal_color.hpp + database/database_dump_service.h data_verification.h database.h database_schema.h @@ -150,6 +153,7 @@ SET(common_headers event_sub.h extprofile.h faction.h + file_util.h features.h fixed_memory_hash_set.h fixed_memory_variable_hash_set.h diff --git a/common/cli/eqemu_command_handler.cpp b/common/cli/eqemu_command_handler.cpp index bbcdb4612..96ed5a155 100644 --- a/common/cli/eqemu_command_handler.cpp +++ b/common/cli/eqemu_command_handler.cpp @@ -96,7 +96,7 @@ namespace EQEmuCommand { "\nCommand" << termcolor::reset << "\n\n" << termcolor::green << argv[1] << arguments_string << termcolor::reset << "\n" << - termcolor::yellow << (!options_string.empty() ? "\nOptions\n" : "") << + termcolor::yellow << (!options_string.empty() ? "\nOptions\n\n" : "") << termcolor::reset << termcolor::cyan << options_string << termcolor::reset; std::cout << command_string.str() << std::endl; diff --git a/common/database.cpp b/common/database.cpp index 352ecbbc1..3f64a21f9 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -2158,6 +2158,44 @@ uint32 Database::GetGuildIDByCharID(uint32 character_id) return atoi(row[0]); } +uint32 Database::GetGroupIDByCharID(uint32 character_id) +{ + std::string query = fmt::format( + SQL( + SELECT groupid + FROM group_id + WHERE charid = '{}' + ), + character_id + ); + auto results = QueryDatabase(query); + + if (!results.Success()) + return 0; + + if (results.RowCount() == 0) + return 0; + + auto row = results.begin(); + return atoi(row[0]); +} + +uint32 Database::GetRaidIDByCharID(uint32 character_id) { + std::string query = fmt::format( + SQL( + SELECT raidid + FROM raid_members + WHERE charid = '{}' + ), + character_id + ); + auto results = QueryDatabase(query); + for (auto row = results.begin(); row != results.end(); ++row) { + return atoi(row[0]); + } + return 0; +} + /** * @param log_settings */ diff --git a/common/database.h b/common/database.h index f569da20b..95458c1c1 100644 --- a/common/database.h +++ b/common/database.h @@ -133,6 +133,8 @@ public: uint32 GetCharacterID(const char *name); uint32 GetCharacterInfo(const char* iName, uint32* oAccID = 0, uint32* oZoneID = 0, uint32* oInstanceID = 0, float* oX = 0, float* oY = 0, float* oZ = 0); uint32 GetGuildIDByCharID(uint32 char_id); + uint32 GetGroupIDByCharID(uint32 char_id); + uint32 GetRaidIDByCharID(uint32 char_id); void GetAccountName(uint32 accountid, char* name, uint32* oLSAccountID = 0); void GetCharName(uint32 char_id, char* name); diff --git a/common/database/database_dump_service.cpp b/common/database/database_dump_service.cpp new file mode 100644 index 000000000..d505a1a24 --- /dev/null +++ b/common/database/database_dump_service.cpp @@ -0,0 +1,569 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY except by those people which sell it, which + * are required to give you total support for your newly bought product; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#include +#include +#include +#include "database_dump_service.h" +#include "../eqemu_logsys.h" +#include "../string_util.h" +#include "../eqemu_config.h" +#include "../database_schema.h" +#include "../file_util.h" + +#include + +#if _WIN32 +#include +#else + +#include + +#endif + +#define DATABASE_DUMP_PATH "backups/" + +/** + * @param cmd + * @param return_result + * @return + */ +std::string DatabaseDumpService::execute(const std::string &cmd, bool return_result = true) +{ + const char *file_name = "db-exec-result.txt"; + + if (return_result) { +#ifdef _WINDOWS + std::system((cmd + " > " + file_name + " 2>&1").c_str()); +#else + std::system((cmd + " > " + file_name).c_str()); +#endif + } + else { + std::system((cmd).c_str()); + } + + std::string result; + + if (return_result) { + std::ifstream file(file_name); + result = {std::istreambuf_iterator(file), std::istreambuf_iterator()}; + std::remove(file_name); + + } + + return result; +} + +/** + * @return bool + */ +bool DatabaseDumpService::IsMySQLInstalled() +{ + std::string version_output = GetMySQLVersion(); + + return version_output.find("mysql") != std::string::npos && version_output.find("Ver") != std::string::npos; +} + +/** + * Linux + * @return bool + */ +bool DatabaseDumpService::IsTarAvailable() +{ + std::string version_output = execute("tar --version"); + + return version_output.find("GNU tar") != std::string::npos; +} + +/** + * Windows + * @return bool + */ +bool DatabaseDumpService::Is7ZipAvailable() +{ + std::string version_output = execute("7z --help"); + + return version_output.find("7-Zip") != std::string::npos; +} + +/** + * @return + */ +bool DatabaseDumpService::HasCompressionBinary() +{ + return IsTarAvailable() || Is7ZipAvailable(); +} + +/** + * @return + */ +std::string DatabaseDumpService::GetMySQLVersion() +{ + std::string version_output = execute("mysql --version"); + + return trim(version_output); +} + +/** + * @return + */ +std::string DatabaseDumpService::GetBaseMySQLDumpCommand() +{ + auto config = EQEmuConfig::get(); + + return fmt::format( + "mysqldump -u {} -p{} -h {} {}", + config->DatabaseUsername, + config->DatabasePassword, + config->DatabaseHost, + config->DatabaseDB + ); +} + +/** + * @return + */ +std::string DatabaseDumpService::GetPlayerTablesList() +{ + std::string tables_list; + std::vector tables = DatabaseSchema::GetPlayerTables(); + for (const auto &table : tables) { + tables_list += table + " "; + } + + return trim(tables_list); +} + +/** + * @return + */ +std::string DatabaseDumpService::GetLoginTableList() +{ + std::string tables_list; + std::vector tables = DatabaseSchema::GetLoginTables(); + for (const auto &table : tables) { + tables_list += table + " "; + } + + return trim(tables_list); +} + +/** + * @return + */ +std::string DatabaseDumpService::GetQueryServTables() +{ + std::string tables_list; + std::vector tables = DatabaseSchema::GetQueryServerTables(); + for (const auto &table : tables) { + tables_list += table + " "; + } + + return trim(tables_list); +} + +/** + * @return + */ +std::string DatabaseDumpService::GetSystemTablesList() +{ + std::string tables_list; + + std::vector tables = DatabaseSchema::GetServerTables(); + for (const auto &table : tables) { + tables_list += table + " "; + } + + tables = DatabaseSchema::GetVersionTables(); + for (const auto &table : tables) { + tables_list += table + " "; + } + + return trim(tables_list); +} +/** + * @return + */ +std::string DatabaseDumpService::GetStateTablesList() +{ + std::string tables_list; + + std::vector tables = DatabaseSchema::GetStateTables(); + for (const auto &table : tables) { + tables_list += table + " "; + } + + return trim(tables_list); +} + +/** + * @return + */ +std::string DatabaseDumpService::GetContentTablesList() +{ + std::string tables_list; + + std::vector tables = DatabaseSchema::GetContentTables(); + for (const auto &table : tables) { + tables_list += table + " "; + } + + return trim(tables_list); +} + +/** + * @return + */ +std::string GetDumpDate() +{ + + time_t now = time(nullptr); + struct tm time_struct{}; + char buf[80]; + time_struct = *localtime(&now); + strftime(buf, sizeof(buf), "%Y-%m-%d", &time_struct); + + std::string time = buf; + + return time; +} + +/** + * @return + */ +std::string DatabaseDumpService::GetSetDumpPath() +{ + return !GetDumpPath().empty() ? GetDumpPath() : DATABASE_DUMP_PATH; +} +/** + * @return + */ +std::string DatabaseDumpService::GetDumpFileNameWithPath() +{ + return GetSetDumpPath() + GetDumpFileName(); +} + +void DatabaseDumpService::Dump() +{ + if (!IsMySQLInstalled()) { + LogError("MySQL is not installed; Please check your PATH for a valid MySQL installation"); + return; + } + + if (IsDumpDropTableSyntaxOnly()) { + SetDumpOutputToConsole(true); + } + + if (IsDumpOutputToConsole()) { + LogSys.SilenceConsoleLogging(); + } + + LogInfo("MySQL installed [{}]", GetMySQLVersion()); + + SetDumpFileName(EQEmuConfig::get()->DatabaseDB + '-' + GetDumpDate()); + + auto config = EQEmuConfig::get(); + + LogInfo( + "Database [{}] Host [{}] Username [{}]", + config->DatabaseDB, + config->DatabaseHost, + config->DatabaseUsername + ); + + std::string options = "--allow-keywords --extended-insert"; + + if (IsDumpWithNoData()) { + options += " --no-data"; + } + + if (!IsDumpTableLock()) { + options += " --skip-lock-tables"; + } + + std::string tables_to_dump; + std::string dump_descriptor; + + if (!IsDumpAllTables()) { + if (IsDumpPlayerTables()) { + tables_to_dump += GetPlayerTablesList() + " "; + dump_descriptor += "-player"; + } + + if (IsDumpSystemTables()) { + tables_to_dump += GetSystemTablesList() + " "; + dump_descriptor += "-system"; + } + + if (IsDumpStateTables()) { + tables_to_dump += GetStateTablesList() + " "; + dump_descriptor += "-state"; + } + + if (IsDumpContentTables()) { + tables_to_dump += GetContentTablesList() + " "; + dump_descriptor += "-content"; + } + + if (IsDumpLoginServerTables()) { + tables_to_dump += GetLoginTableList() + " "; + dump_descriptor += "-login"; + } + + if (IsDumpQueryServerTables()) { + tables_to_dump += GetQueryServTables(); + dump_descriptor += "-queryserv"; + } + } + + if (!dump_descriptor.empty()) { + SetDumpFileName(GetDumpFileName() + dump_descriptor); + } + + /** + * If we are dumping to stdout then we don't generate a file + */ + std::string pipe_file; + if (!IsDumpOutputToConsole()) { + pipe_file = fmt::format(" > {}.sql", GetDumpFileNameWithPath()); + } + + std::string execute_command = fmt::format( + "{} {} {} {}", + GetBaseMySQLDumpCommand(), + options, + tables_to_dump, + pipe_file + ); + + if (!FileUtil::exists(GetSetDumpPath()) && !IsDumpOutputToConsole()) { + FileUtil::mkdir(GetSetDumpPath()); + } + + if (IsDumpDropTableSyntaxOnly()) { + std::vector tables = SplitString(tables_to_dump, ' '); + + for (auto &table : tables) { + std::cout << "DROP TABLE IF EXISTS `" << table << "`;" << std::endl; + } + + if (tables_to_dump.empty()) { + std::cerr << "No tables were specified" << std::endl; + } + } + else { + std::string execution_result = execute(execute_command, IsDumpOutputToConsole()); + if (!execution_result.empty()) { + std::cout << execution_result; + } + } + + if (!tables_to_dump.empty()) { + LogInfo("Dumping Tables [{}]", tables_to_dump); + } + + LogInfo("Database dump created at [{}.sql]", GetDumpFileNameWithPath()); + + if (IsDumpWithCompression() && !IsDumpOutputToConsole()) { + if (HasCompressionBinary()) { + LogInfo("Compression requested... Compressing dump [{}.sql]", GetDumpFileNameWithPath()); + + if (IsTarAvailable()) { + execute( + fmt::format( + "tar -zcvf {}.tar.gz -C {} {}.sql", + GetDumpFileNameWithPath(), + GetSetDumpPath(), + GetDumpFileName() + ) + ); + LogInfo("Compressed dump created at [{}.tar.gz]", GetDumpFileNameWithPath()); + } + else if (Is7ZipAvailable()) { + execute( + fmt::format( + "7z a -t7z {}.zip {}.sql", + GetDumpFileNameWithPath(), + GetDumpFileNameWithPath() + ) + ); + LogInfo("Compressed dump created at [{}.zip]", GetDumpFileNameWithPath()); + } + else { + LogInfo("Compression requested, but no available compression binary was found"); + } + } + else { + LogWarning("Compression requested but binary not found... Skipping..."); + } + } + +// LogDebug("[{}] dump-to-console", IsDumpOutputToConsole()); +// LogDebug("[{}] dump-path", GetSetDumpPath()); +// LogDebug("[{}] compression", (IsDumpWithCompression() ? "true" : "false")); +// LogDebug("[{}] query-serv", (IsDumpQueryServerTables() ? "true" : "false")); +// LogDebug("[{}] has-compression-binary", (HasCompressionBinary() ? "true" : "false")); +// LogDebug("[{}] content", (IsDumpContentTables() ? "true" : "false")); +// LogDebug("[{}] no-data", (IsDumpWithNoData() ? "true" : "false")); +// LogDebug("[{}] login", (IsDumpLoginServerTables() ? "true" : "false")); +// LogDebug("[{}] player", (IsDumpPlayerTables() ? "true" : "false")); +// LogDebug("[{}] system", (IsDumpSystemTables() ? "true" : "false")); +} + +bool DatabaseDumpService::IsDumpSystemTables() const +{ + return dump_system_tables; +} + +void DatabaseDumpService::SetDumpSystemTables(bool dump_system_tables) +{ + DatabaseDumpService::dump_system_tables = dump_system_tables; +} + +bool DatabaseDumpService::IsDumpContentTables() const +{ + return dump_content_tables; +} + +void DatabaseDumpService::SetDumpContentTables(bool dump_content_tables) +{ + DatabaseDumpService::dump_content_tables = dump_content_tables; +} + +bool DatabaseDumpService::IsDumpPlayerTables() const +{ + return dump_player_tables; +} + +void DatabaseDumpService::SetDumpPlayerTables(bool dump_player_tables) +{ + DatabaseDumpService::dump_player_tables = dump_player_tables; +} + +bool DatabaseDumpService::IsDumpLoginServerTables() const +{ + return dump_login_server_tables; +} + +void DatabaseDumpService::SetDumpLoginServerTables(bool dump_login_server_tables) +{ + DatabaseDumpService::dump_login_server_tables = dump_login_server_tables; +} + +bool DatabaseDumpService::IsDumpWithNoData() const +{ + return dump_with_no_data; +} + +void DatabaseDumpService::SetDumpWithNoData(bool dump_with_no_data) +{ + DatabaseDumpService::dump_with_no_data = dump_with_no_data; +} + +bool DatabaseDumpService::IsDumpAllTables() const +{ + return dump_all_tables; +} + +void DatabaseDumpService::SetDumpAllTables(bool dump_all_tables) +{ + DatabaseDumpService::dump_all_tables = dump_all_tables; +} + +bool DatabaseDumpService::IsDumpTableLock() const +{ + return dump_table_lock; +} + +void DatabaseDumpService::SetDumpTableLock(bool dump_table_lock) +{ + DatabaseDumpService::dump_table_lock = dump_table_lock; +} + +bool DatabaseDumpService::IsDumpWithCompression() const +{ + return dump_with_compression; +} + +void DatabaseDumpService::SetDumpWithCompression(bool dump_with_compression) +{ + DatabaseDumpService::dump_with_compression = dump_with_compression; +} + +const std::string &DatabaseDumpService::GetDumpPath() const +{ + return dump_path; +} + +void DatabaseDumpService::SetDumpPath(const std::string &dump_path) +{ + DatabaseDumpService::dump_path = dump_path; +} + +void DatabaseDumpService::SetDumpFileName(const std::string &dump_file_name) +{ + DatabaseDumpService::dump_file_name = dump_file_name; +} + +const std::string &DatabaseDumpService::GetDumpFileName() const +{ + return dump_file_name; +} + +bool DatabaseDumpService::IsDumpQueryServerTables() const +{ + return dump_query_server_tables; +} + +void DatabaseDumpService::SetDumpQueryServerTables(bool dump_query_server_tables) +{ + DatabaseDumpService::dump_query_server_tables = dump_query_server_tables; +} + +bool DatabaseDumpService::IsDumpOutputToConsole() const +{ + return dump_output_to_console; +} + +void DatabaseDumpService::SetDumpOutputToConsole(bool dump_output_to_console) +{ + DatabaseDumpService::dump_output_to_console = dump_output_to_console; +} + +bool DatabaseDumpService::IsDumpDropTableSyntaxOnly() const +{ + return dump_drop_table_syntax_only; +} + +void DatabaseDumpService::SetDumpDropTableSyntaxOnly(bool dump_drop_table_syntax_only) +{ + DatabaseDumpService::dump_drop_table_syntax_only = dump_drop_table_syntax_only; +} + +bool DatabaseDumpService::IsDumpStateTables() const +{ + return dump_state_tables; +} + +void DatabaseDumpService::SetDumpStateTables(bool dump_state_tables) +{ + DatabaseDumpService::dump_state_tables = dump_state_tables; +} diff --git a/common/database/database_dump_service.h b/common/database/database_dump_service.h new file mode 100644 index 000000000..f0614e85b --- /dev/null +++ b/common/database/database_dump_service.h @@ -0,0 +1,91 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY except by those people which sell it, which + * are required to give you total support for your newly bought product; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#ifndef EQEMU_DATABASE_DUMP_SERVICE_H +#define EQEMU_DATABASE_DUMP_SERVICE_H + + +class DatabaseDumpService { +public: + void Dump(); + bool IsDumpAllTables() const; + void SetDumpAllTables(bool dump_all_tables); + bool IsDumpWithNoData() const; + void SetDumpWithNoData(bool dump_with_no_data); + bool IsDumpSystemTables() const; + void SetDumpSystemTables(bool dump_system_tables); + bool IsDumpContentTables() const; + void SetDumpContentTables(bool dump_content_tables); + bool IsDumpPlayerTables() const; + void SetDumpPlayerTables(bool dump_player_tables); + bool IsDumpLoginServerTables() const; + void SetDumpLoginServerTables(bool dump_login_server_tables); + bool IsDumpTableLock() const; + void SetDumpTableLock(bool dump_table_lock); + bool IsDumpWithCompression() const; + void SetDumpWithCompression(bool dump_with_compression); + const std::string &GetDumpPath() const; + void SetDumpPath(const std::string &dump_path); + const std::string &GetDumpFileName() const; + void SetDumpFileName(const std::string &dump_file_name); + bool IsDumpQueryServerTables() const; + void SetDumpQueryServerTables(bool dump_query_server_tables); + bool IsDumpOutputToConsole() const; + void SetDumpOutputToConsole(bool dump_output_to_console); + bool IsDumpDropTableSyntaxOnly() const; + void SetDumpDropTableSyntaxOnly(bool dump_drop_table_syntax_only); + bool IsDumpStateTables() const; + void SetDumpStateTables(bool dump_state_tables); + +private: + bool dump_all_tables = false; + bool dump_state_tables = false; + bool dump_system_tables = false; + bool dump_content_tables = false; + bool dump_player_tables = false; + bool dump_query_server_tables = false; + bool dump_login_server_tables = false; + bool dump_with_no_data = false; + bool dump_table_lock = false; + bool dump_with_compression = false; + bool dump_output_to_console = false; + bool dump_drop_table_syntax_only = false; + std::string dump_path; + std::string dump_file_name; + + std::string execute(const std::string &cmd, bool return_result); + bool IsMySQLInstalled(); + std::string GetMySQLVersion(); + std::string GetBaseMySQLDumpCommand(); + std::string GetPlayerTablesList(); + std::string GetSystemTablesList(); + std::string GetStateTablesList(); + std::string GetContentTablesList(); + std::string GetLoginTableList(); + bool IsTarAvailable(); + bool Is7ZipAvailable(); + bool HasCompressionBinary(); + std::string GetDumpFileNameWithPath(); + std::string GetSetDumpPath(); + std::string GetQueryServTables(); +}; + + +#endif //EQEMU_DATABASE_DUMP_SERVICE_H diff --git a/common/database_schema.h b/common/database_schema.h index b497ad474..7f883aa9f 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -85,7 +85,8 @@ namespace DatabaseSchema { } /** - * Gets all player and meta-data tables + * @description Gets all player and meta-data tables + * @note These tables have no content in the PEQ daily dump * * @return */ @@ -129,6 +130,7 @@ namespace DatabaseSchema { "character_tribute", "completed_tasks", "data_buckets", + "discovered_items", "faction_values", "friends", "guild_bank", @@ -141,9 +143,12 @@ namespace DatabaseSchema { "inventory_snapshots", "keyring", "mail", + "petitions", "player_titlesets", "quest_globals", "sharedbank", + "spell_buckets", + "spell_globals", "timers", "titles", "trader", @@ -233,7 +238,6 @@ namespace DatabaseSchema { "task_activities", "tasks", "tasksets", - "titles", "tradeskill_recipe", "tradeskill_recipe_entries", "traps", @@ -255,33 +259,49 @@ namespace DatabaseSchema { static std::vector GetServerTables() { return { - "banned_ips", - "bugs", - "bug_reports", + "chatchannels", "command_settings", "db_str", - "discovered_items", "eqtime", - "eventlog", - "gm_ips", - "hackers", - "ip_exemptions", "launcher", "launcher_zones", "level_exp_mods", "logsys_categories", "name_filter", "perl_event_export_settings", - "petitions", "profanity_list", - "reports", "rule_sets", "rule_values", - "saylink", "variables", }; } + /** + * Gets QueryServer tables + * + * @return + */ + static std::vector GetQueryServerTables() + { + return { + "qs_merchant_transaction_record", + "qs_merchant_transaction_record_entries", + "qs_player_aa_rate_hourly", + "qs_player_delete_record", + "qs_player_delete_record_entries", + "qs_player_events", + "qs_player_handin_record", + "qs_player_handin_record_entries", + "qs_player_move_record", + "qs_player_move_record_entries", + "qs_player_npc_kill_record", + "qs_player_npc_kill_record_entries", + "qs_player_speech", + "qs_player_trade_record", + "qs_player_trade_record_entries", + }; + } + /** * Gets state tables * Tables that keep track of server state @@ -292,9 +312,15 @@ namespace DatabaseSchema { { return { "adventure_members", - "chatchannels", + "banned_ips", + "bug_reports", + "bugs", + "eventlog", + "gm_ips", "group_id", "group_leaders", + "hackers", + "ip_exemptions", "item_tick", "lfguild", "merchantlist_temp", @@ -302,9 +328,10 @@ namespace DatabaseSchema { "raid_details", "raid_leaders", "raid_members", + "reports", "respawn_times", - "spell_buckets", - "spell_globals", + "saylink", + }; } diff --git a/common/event/event_loop.h b/common/event/event_loop.h index 295c0532d..537c3280a 100644 --- a/common/event/event_loop.h +++ b/common/event/event_loop.h @@ -25,6 +25,10 @@ namespace EQ uv_run(&m_loop, UV_RUN_DEFAULT); } + void Shutdown() { + uv_stop(&m_loop); + } + uv_loop_t* Handle() { return &m_loop; } private: diff --git a/common/file_util.cpp b/common/file_util.cpp new file mode 100644 index 000000000..8c9aca9a4 --- /dev/null +++ b/common/file_util.cpp @@ -0,0 +1,67 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY except by those people which sell it, which + * are required to give you total support for your newly bought product; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#include +#include "file_util.h" + +#ifdef _WINDOWS +#include +#include +#include +#include +#include +#include +#else + +#include +#include + +#endif + +/** + * @param name + * @return + */ +bool FileUtil::exists(const std::string &name) +{ + std::ifstream f(name.c_str()); + + return f.good(); +} + +/** + * @param directory_name + */ +void FileUtil::mkdir(const std::string& directory_name) +{ + +#ifdef _WINDOWS + struct _stat st; + if (_stat(directory_name.c_str(), &st) == 0) // exists + return; + _mkdir(directory_name.c_str()); +#else + struct stat st{}; + if (stat(directory_name.c_str(), &st) == 0) { // exists + return; + } + ::mkdir(directory_name.c_str(), 0755); +#endif +} \ No newline at end of file diff --git a/common/file_util.h b/common/file_util.h new file mode 100644 index 000000000..05869d303 --- /dev/null +++ b/common/file_util.h @@ -0,0 +1,32 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY except by those people which sell it, which + * are required to give you total support for your newly bought product; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ + +#ifndef EQEMU_FILE_UTIL_H +#define EQEMU_FILE_UTIL_H + + +class FileUtil { +public: + static bool exists(const std::string &name); + static void mkdir(const std::string& directory_name); +}; + + +#endif //EQEMU_FILE_UTIL_H diff --git a/common/guild_base.cpp b/common/guild_base.cpp index 642db7cbc..b7ac8515c 100644 --- a/common/guild_base.cpp +++ b/common/guild_base.cpp @@ -912,7 +912,7 @@ bool BaseGuildManager::GetEntireGuild(uint32 guild_id, std::vectorQueryDatabase(query); if (!results.Success()) { return false; @@ -941,7 +941,7 @@ bool BaseGuildManager::GetCharInfo(const char *char_name, CharGuildInfo &into) { m_db->DoEscapeString(esc, char_name, nl); //load up the rank info for each guild. - std::string query = StringFormat(GuildMemberBaseQuery " WHERE c.name='%s'", esc); + std::string query = StringFormat(GuildMemberBaseQuery " WHERE c.name='%s' AND c.deleted_at IS NULL", esc); safe_delete_array(esc); auto results = m_db->QueryDatabase(query); if (!results.Success()) { @@ -969,9 +969,9 @@ bool BaseGuildManager::GetCharInfo(uint32 char_id, CharGuildInfo &into) { //load up the rank info for each guild. std::string query; #ifdef BOTS - query = StringFormat(GuildMemberBaseQuery " WHERE c.id=%d AND c.mob_type = 'C'", char_id); + query = StringFormat(GuildMemberBaseQuery " WHERE c.id=%d AND c.mob_type = 'C' AND c.deleted_at IS NULL", char_id); #else - query = StringFormat(GuildMemberBaseQuery " WHERE c.id=%d", char_id); + query = StringFormat(GuildMemberBaseQuery " WHERE c.id=%d AND c.deleted_at IS NULL", char_id); #endif auto results = m_db->QueryDatabase(query); if (!results.Success()) { diff --git a/common/version.h b/common/version.h index 359c27576..7c378e763 100644 --- a/common/version.h +++ b/common/version.h @@ -34,10 +34,10 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9151 +#define CURRENT_BINARY_DATABASE_VERSION 9152 #ifdef BOTS - #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9026 + #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9027 #else #define CURRENT_BINARY_BOTS_DATABASE_VERSION 0 // must be 0 #endif diff --git a/utils/scripts/eqemu_server.pl b/utils/scripts/eqemu_server.pl index a5ae9e7b8..6618b021e 100755 --- a/utils/scripts/eqemu_server.pl +++ b/utils/scripts/eqemu_server.pl @@ -52,6 +52,10 @@ if (-e "eqemu_server_skip_update.txt") { $skip_self_update_check = 1; } +if (-e "eqemu_server_skip_maps_update.txt") { + $skip_self_maps_update_check = 1; +} + #::: Check for script self update check_xml_to_json_conversion() if $ARGV[0] eq "convert_xml"; do_self_update_check_routine() if !$skip_self_update_check; @@ -460,7 +464,7 @@ sub do_installer_routines { get_remote_file($install_repository_request_url . "libmysql.dll", "libmysql.dll", 1); } - map_files_fetch_bulk(); + map_files_fetch_bulk() if !$skip_self_maps_update_check; opcodes_fetch(); plugins_fetch(); quest_files_fetch(); @@ -533,7 +537,10 @@ sub check_for_world_bootup_database_update { if ($binary_database_version == $local_database_version && $ARGV[0] eq "ran_from_world") { print "[Update] Database up to date...\n"; - exit; + if (trim($db_version[2]) == 0) { + print "[Update] Continuing bootup\n"; + exit; + } } else { #::: We ran world - Database needs to update, lets backup and run updates and continue world bootup @@ -1705,26 +1712,22 @@ sub fetch_server_dlls { sub fetch_peq_db_full { print "[Install] Downloading latest PEQ Database... Please wait...\n"; - get_remote_file("http://edit.projecteq.net/weekly/peq_beta.zip", "updates_staged/peq_beta.zip", 1); + get_remote_file("http://db.projecteq.net/api/v1/dump/latest", "updates_staged/peq-latest.zip", 1); print "[Install] Downloaded latest PEQ Database... Extracting...\n"; - unzip('updates_staged/peq_beta.zip', 'updates_staged/peq_db/'); - my $start_dir = "updates_staged/peq_db"; + unzip('updates_staged/peq-latest.zip', 'updates_staged/peq_db/'); + my $start_dir = "updates_staged/peq_db/peq-dump"; find( sub { push @files, $File::Find::name unless -d; }, $start_dir ); for my $file (@files) { $destination_file = $file; - $destination_file =~ s/updates_staged\/peq_db\///g; - if ($file =~ /peqbeta|player_tables/i) { + $destination_file =~ s/updates_staged\/peq_db\/peq-dump\///g; + if ($file =~ /create_tables_content|create_tables_login|create_tables_player|create_tables_queryserv|create_tables_state|create_tables_system/i) { print "[Install] DB :: Installing :: " . $destination_file . "\n"; get_mysql_result_from_file($file); } } - - #::: PEQ DB baseline version - print get_mysql_result("DELETE FROM db_version"); - print get_mysql_result("INSERT INTO `db_version` (`version`) VALUES (9130);"); } sub map_files_fetch_bulk { diff --git a/utils/sql/character_table_list.txt b/utils/sql/character_table_list.txt deleted file mode 100644 index ab6610dab..000000000 --- a/utils/sql/character_table_list.txt +++ /dev/null @@ -1,57 +0,0 @@ -account -account_ip -account_flags -account_rewards -adventure_details -adventure_stats -buyer -char_recipe_list -character_activities -character_alt_currency -character_alternate_abilities -character_auras -character_bandolier -character_bind -character_buffs -character_corpse_items -character_corpses -character_currency -character_data -character_disciplines -character_enabledtasks -character_inspect_messages -character_item_recast -character_languages -character_leadership_abilities -character_material -character_memmed_spells -character_pet_buffs -character_pet_info -character_pet_inventory -character_potionbelt -character_skills -character_spells -character_tasks -character_tribute -completed_tasks -data_buckets -faction_values -friends -guild_bank -guild_members -guild_ranks -guild_relations -guilds -instance_list_player -inventory -inventory_snapshots -keyring -mail -player_titlesets -quest_globals -sharedbank -timers -titles -trader -trader_audit -zone_flags" \ No newline at end of file diff --git a/utils/sql/data_tables.txt b/utils/sql/data_tables.txt deleted file mode 100644 index 8738c716b..000000000 --- a/utils/sql/data_tables.txt +++ /dev/null @@ -1,6 +0,0 @@ -command_settings -inventory_versions -launcher -rule_sets -rule_values -variables \ No newline at end of file diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index ca7a29f25..28b22028e 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -404,7 +404,8 @@ 9148|2020_01_28_corpse_guild_consent_id.sql|SHOW COLUMNS FROM `character_corpses` LIKE 'guild_consent_id'|empty| 9149|2020_02_06_globalloot.sql|SHOW COLUMNS FROM `global_loot` LIKE 'hot_zone'|empty| 9150|2020_02_06_aa_reset_on_death.sql|SHOW COLUMNS FROM `aa_ability` LIKE 'reset_on_death'|empty| -9151|2020_03_05_npc_always_aggro.sql|SHOW COLUMNS FROM `npc_types` LIKE 'always_aggros_foes'|empty| +9151|2020_03_05_npc_always_aggro.sql|SHOW COLUMNS FROM `npc_types` LIKE 'always_aggro'|empty| +9152|2020_03_09_convert_myisam_to_innodb.sql|SELECT * FROM db_version WHERE version >= 9152|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/bots/bots_db_update_manifest.txt b/utils/sql/git/bots/bots_db_update_manifest.txt index 1e18516b7..86e7c49dd 100644 --- a/utils/sql/git/bots/bots_db_update_manifest.txt +++ b/utils/sql/git/bots/bots_db_update_manifest.txt @@ -25,6 +25,7 @@ 9024|2019_06_27_bots_pet_get_lost.sql|SELECT `bot_command` FROM `bot_command_settings` WHERE `bot_command` LIKE 'petgetlost'|empty| 9025|2019_08_26_bots_owner_option_spawn_message.sql|SELECT * FROM db_version WHERE bots_version >= 9025|empty| 9026|2019_09_09_bots_owner_options_rework.sql|SHOW COLUMNS FROM `bot_owner_options` LIKE 'option_type'|empty| +9027|2020_03_30_bots_view_update.sql|SELECT * FROM db_version WHERE bots_version >= 9027|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/bots/required/2020_03_30_bots_view_update.sql b/utils/sql/git/bots/required/2020_03_30_bots_view_update.sql new file mode 100644 index 000000000..d4948efed --- /dev/null +++ b/utils/sql/git/bots/required/2020_03_30_bots_view_update.sql @@ -0,0 +1,24 @@ +DROP VIEW IF EXISTS `vw_bot_character_mobs`; + +-- Views +CREATE VIEW `vw_bot_character_mobs` AS +SELECT +_utf8'C' AS mob_type, +c.`id`, +c.`name`, +c.`class`, +c.`level`, +c.`last_login`, +c.`zone_id`, +c.`deleted_at` +FROM `character_data` AS c +UNION ALL +SELECT _utf8'B' AS mob_type, +b.`bot_id` AS id, +b.`name`, +b.`class`, +b.`level`, +b.`last_spawn` AS last_login, +b.`zone_id`, +NULL AS `deleted_at` +FROM `bot_data` AS b; diff --git a/utils/sql/git/required/2020_03_05_npc_always_aggro.sql b/utils/sql/git/required/2020_03_05_npc_always_aggro.sql index d9e6351a3..83641998c 100644 --- a/utils/sql/git/required/2020_03_05_npc_always_aggro.sql +++ b/utils/sql/git/required/2020_03_05_npc_always_aggro.sql @@ -1 +1 @@ -ALTER TABLE `npc_types` ADD COLUMN `always_aggros_foes` tinyint(4) NOT NULL DEFAULT 0; +ALTER TABLE `npc_types` ADD COLUMN `always_aggro` tinyint(1) NOT NULL DEFAULT 0; diff --git a/utils/sql/git/required/2020_03_09_convert_myisam_to_innodb.sql b/utils/sql/git/required/2020_03_09_convert_myisam_to_innodb.sql new file mode 100644 index 000000000..31ccf1094 --- /dev/null +++ b/utils/sql/git/required/2020_03_09_convert_myisam_to_innodb.sql @@ -0,0 +1,95 @@ +ALTER TABLE `account_flags` ENGINE=InnoDB; +ALTER TABLE `account_ip` ENGINE=InnoDB; +ALTER TABLE `account` ENGINE=InnoDB; +ALTER TABLE `adventure_template_entry_flavor` ENGINE=InnoDB; +ALTER TABLE `adventure_template_entry` ENGINE=InnoDB; +ALTER TABLE `altadv_vars` ENGINE=InnoDB; +ALTER TABLE `alternate_currency` ENGINE=InnoDB; +ALTER TABLE `banned_ips` ENGINE=InnoDB; +ALTER TABLE `base_data` ENGINE=InnoDB; +ALTER TABLE `blocked_spells` ENGINE=InnoDB; +ALTER TABLE `buyer` ENGINE=InnoDB; +ALTER TABLE `char_create_combinations` ENGINE=InnoDB; +ALTER TABLE `char_create_point_allocations` ENGINE=InnoDB; +ALTER TABLE `character_activities` ENGINE=InnoDB; +ALTER TABLE `character_enabledtasks` ENGINE=InnoDB; +ALTER TABLE `character_tasks` ENGINE=InnoDB; +ALTER TABLE `chatchannels` ENGINE=InnoDB; +ALTER TABLE `completed_tasks` ENGINE=InnoDB; +ALTER TABLE `damageshieldtypes` ENGINE=InnoDB; +ALTER TABLE `discovered_items` ENGINE=InnoDB; +ALTER TABLE `eqtime` ENGINE=InnoDB; +ALTER TABLE `eventlog` ENGINE=InnoDB; +ALTER TABLE `faction_list_mod` ENGINE=InnoDB; +ALTER TABLE `faction_list` ENGINE=InnoDB; +ALTER TABLE `faction_values` ENGINE=InnoDB; +ALTER TABLE `friends` ENGINE=InnoDB; +ALTER TABLE `goallists` ENGINE=InnoDB; +ALTER TABLE `guild_bank` ENGINE=InnoDB; +ALTER TABLE `guild_members` ENGINE=InnoDB; +ALTER TABLE `guild_ranks` ENGINE=InnoDB; +ALTER TABLE `guild_relations` ENGINE=InnoDB; +ALTER TABLE `guilds` ENGINE=InnoDB; +ALTER TABLE `hackers` ENGINE=InnoDB; +ALTER TABLE `horses` ENGINE=InnoDB; +ALTER TABLE `inventory_versions` ENGINE=InnoDB; +ALTER TABLE `item_tick` ENGINE=InnoDB; +ALTER TABLE `items` ENGINE=InnoDB; +ALTER TABLE `keyring` ENGINE=InnoDB; +ALTER TABLE `launcher_zones` ENGINE=InnoDB; +ALTER TABLE `launcher` ENGINE=InnoDB; +ALTER TABLE `ldon_trap_entries` ENGINE=InnoDB; +ALTER TABLE `ldon_trap_templates` ENGINE=InnoDB; +ALTER TABLE `lfguild` ENGINE=InnoDB; +ALTER TABLE `lootdrop_entries` ENGINE=InnoDB; +ALTER TABLE `lootdrop` ENGINE=InnoDB; +ALTER TABLE `loottable_entries` ENGINE=InnoDB; +ALTER TABLE `loottable` ENGINE=InnoDB; +ALTER TABLE `mail` ENGINE=InnoDB; +ALTER TABLE `merc_armorinfo` ENGINE=InnoDB; +ALTER TABLE `merc_buffs` ENGINE=InnoDB; +ALTER TABLE `merc_inventory` ENGINE=InnoDB; +ALTER TABLE `merc_merchant_entries` ENGINE=InnoDB; +ALTER TABLE `merc_merchant_template_entries` ENGINE=InnoDB; +ALTER TABLE `merc_merchant_templates` ENGINE=InnoDB; +ALTER TABLE `merc_name_types` ENGINE=InnoDB; +ALTER TABLE `merc_npc_types` ENGINE=InnoDB; +ALTER TABLE `merc_spell_list_entries` ENGINE=InnoDB; +ALTER TABLE `merc_spell_lists` ENGINE=InnoDB; +ALTER TABLE `merc_stance_entries` ENGINE=InnoDB; +ALTER TABLE `merc_stats` ENGINE=InnoDB; +ALTER TABLE `merc_subtypes` ENGINE=InnoDB; +ALTER TABLE `merc_templates` ENGINE=InnoDB; +ALTER TABLE `merc_types` ENGINE=InnoDB; +ALTER TABLE `merc_weaponinfo` ENGINE=InnoDB; +ALTER TABLE `mercs` ENGINE=InnoDB; +ALTER TABLE `name_filter` ENGINE=InnoDB; +ALTER TABLE `npc_types` ENGINE=InnoDB; +ALTER TABLE `object_contents` ENGINE=InnoDB; +ALTER TABLE `petitions` ENGINE=InnoDB; +ALTER TABLE `pets_equipmentset_entries` ENGINE=InnoDB; +ALTER TABLE `pets_equipmentset` ENGINE=InnoDB; +ALTER TABLE `player_titlesets` ENGINE=InnoDB; +ALTER TABLE `proximities` ENGINE=InnoDB; +ALTER TABLE `races` ENGINE=InnoDB; +ALTER TABLE `raid_details` ENGINE=InnoDB; +ALTER TABLE `raid_leaders` ENGINE=InnoDB; +ALTER TABLE `raid_members` ENGINE=InnoDB; +ALTER TABLE `rule_sets` ENGINE=InnoDB; +ALTER TABLE `rule_values` ENGINE=InnoDB; +ALTER TABLE `saylink` ENGINE=InnoDB; +ALTER TABLE `sharedbank` ENGINE=InnoDB; +ALTER TABLE `skill_caps` ENGINE=InnoDB; +ALTER TABLE `spell_globals` ENGINE=InnoDB; +ALTER TABLE `spells_new` ENGINE=InnoDB; +ALTER TABLE `task_activities` ENGINE=InnoDB; +ALTER TABLE `tasks` ENGINE=InnoDB; +ALTER TABLE `tasksets` ENGINE=InnoDB; +ALTER TABLE `timers` ENGINE=InnoDB; +ALTER TABLE `titles` ENGINE=InnoDB; +ALTER TABLE `trader_audit` ENGINE=InnoDB; +ALTER TABLE `trader` ENGINE=InnoDB; +ALTER TABLE `tradeskill_recipe_entries` ENGINE=InnoDB; +ALTER TABLE `tradeskill_recipe` ENGINE=InnoDB; +ALTER TABLE `variables` ENGINE=InnoDB; +ALTER TABLE `veteran_reward_templates` ENGINE=InnoDB; \ No newline at end of file diff --git a/utils/sql/peq-dump/peq-dump.sh b/utils/sql/peq-dump/peq-dump.sh new file mode 100755 index 000000000..ccb7b8949 --- /dev/null +++ b/utils/sql/peq-dump/peq-dump.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Run from the context of server directory + +world_path="" + +############################################# +# world path +############################################# +if [ -d "bin" ] +then + world_path="bin/" +fi + +world_bin="${world_path}world" + +echo "World path is [$world_path] bin is [$world_bin]" + +############################################# +# dump +############################################# + +dump_path=/tmp/peq-dump/ +echo "Generating dump path [${dump_path}]" +rm -rf ${dump_path} +mkdir -p ${dump_path} + +############################################# +# generate "drop_" table files +############################################# +echo "Generating [drop_*] table exports..." +bash -c "${world_bin} database:dump --content-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_content.sql" +bash -c "${world_bin} database:dump --login-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_login.sql" +bash -c "${world_bin} database:dump --player-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_player.sql" +bash -c "${world_bin} database:dump --system-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_system.sql" +bash -c "${world_bin} database:dump --state-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_state.sql" +bash -c "${world_bin} database:dump --query-serv-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_queryserv.sql" + +############################################# +# generate "create_" table files +############################################# +echo "Generating [create_*] table exports..." + +# structure only +bash -c "${world_bin} database:dump --login-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_login.sql" +bash -c "${world_bin} database:dump --player-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_player.sql" +bash -c "${world_bin} database:dump --state-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_state.sql" +bash -c "${world_bin} database:dump --query-serv-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_queryserv.sql" + +# with content +bash -c "${world_bin} database:dump --content-tables --dump-output-to-console > ${dump_path}create_tables_content.sql" +bash -c "${world_bin} database:dump --system-tables --dump-output-to-console > ${dump_path}create_tables_system.sql" + +############################################# +# "all" exports +############################################# +bash -c "cd ${dump_path} && ls * | grep create | sed 's/.*/source &;/' > create_all_tables.sql" +bash -c "cd ${dump_path} && ls * | grep drop | sed 's/.*/source &;/' > drop_all_tables.sql" + +############################################# +# zip +############################################# +human_date=$(date +"%B-%d-%Y" | tr '[:upper:]' '[:lower:]') + +echo "Compressing..." +bash -c "cd /tmp/ && rm -rf peq-latest.zip && zip peq-latest.zip peq-dump/* && mv ${dump_path}peq-latest.zip /tmp/peq-latest.zip" + +echo "Cleaning up..." +rm -rf ${dump_path} + +echo "Dump located [/tmp/peq-latest.zip]" \ No newline at end of file diff --git a/utils/sql/system_tables.txt b/utils/sql/system_tables.txt deleted file mode 100644 index a33605130..000000000 --- a/utils/sql/system_tables.txt +++ /dev/null @@ -1,113 +0,0 @@ -aa_ability -aa_actions -aa_effects -aa_rank_effects -aa_rank_prereqs -aa_ranks -aa_required_level_cost -adventure_template -adventure_template_entry -adventure_template_entry_flavor -altadv_vars -alternate_currency -auras -base_data -blocked_spells -books -bug_reports -char_create_combinations -char_create_point_allocations -class_skill -damageshieldtypes -data_buckets -db_str -doors -eqtime -faction_base_data -faction_list -faction_list_mod -fear_hints -fishing -forage -global_loot -goallists -graveyard -grid -grid_entries -ground_spawns -horses -instance_list -items -ip_exemptions -ldon_trap_entries -ldon_trap_templates -level_exp_mods -logsys_categories -lootdrop -lootdrop_entries -loottable -loottable_entries -merc_armorinfo -merc_buffs -merc_inventory -merc_merchant_entries -merc_merchant_template_entries -merc_merchant_templates -merc_name_types -merc_npc_types -merc_spell_list_entries -merc_spell_lists -merc_stance_entries -merc_stats -merc_subtypes -merc_templates -merc_types -merc_weaponinfo -merchantlist -mercs -name_filter -npc_emotes -npc_faction -npc_faction_entries -npc_scale_global_base -npc_spells -npc_spells_effects -npc_spells_effects_entries -npc_spells_entries -npc_types -npc_types_metadata -npc_types_tint -object -perl_event_export_settings -pets -pets_equipmentset -pets_equipmentset_entries -profanity_list -proximities -races -saylink -skill_caps -spawn2 -spawn_condition_values -spawn_conditions -spawn_events -spawnentry -spawngroup -spells_new -start_zones -starting_items -task_activities -tasks -tasksets -titles -tradeskill_recipe -tradeskill_recipe_entries -traps -tribute_levels -tributes -veteran_reward_templates -zone -zone_points -zone_server -zone_state_dump -zoneserver_auth diff --git a/utils/sql/user_tables.txt b/utils/sql/user_tables.txt deleted file mode 100644 index ee19fe472..000000000 --- a/utils/sql/user_tables.txt +++ /dev/null @@ -1,94 +0,0 @@ -aa_timers -account -account_flags -account_ip -account_rewards -adventure_details -adventure_members -adventure_stats -banned_ips -bugs -buyer -char_recipe_list -character_activities -character_alt_currency -character_alternate_abilities -character_auras -character_bandolier -character_bind -character_buffs -character_corpse_items -character_corpses -character_currency -character_data -character_disciplines -character_enabledtasks -character_inspect_messages -character_item_recast -character_languages -character_leadership_abilities -character_material -character_memmed_spells -character_pet_buffs -character_pet_info -character_pet_inventory -character_potionbelt -character_skills -character_spells -character_tasks -character_tribute -chatchannels -completed_tasks -discovered_items -eventlog -faction_values -friends -gm_ips -group_id -group_leaders -guild_bank -guild_members -guild_ranks -guild_relations -guilds -hackers -instance_list_player -inventory -inventory_snapshots -item_tick -keyring -launcher_zones -lfguild -mail -merchantlist_temp -object_contents -petitions -player_titlesets -qs_merchant_transaction_record -qs_merchant_transaction_record_entries -qs_player_aa_rate_hourly -qs_player_delete_record -qs_player_delete_record_entries -qs_player_events -qs_player_handin_record -qs_player_handin_record_entries -qs_player_move_record -qs_player_move_record_entries -qs_player_npc_kill_record -qs_player_npc_kill_record_entries -qs_player_speech -qs_player_trade_record -qs_player_trade_record_entries -quest_globals -raid_details -raid_leaders -raid_members -reports -respawn_times -sharedbank -spell_buckets -spell_globals -timers -trader -trader_audit -zone_flags diff --git a/world/main.cpp b/world/main.cpp index 72cc6d391..9aac1a903 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -415,6 +415,7 @@ int main(int argc, char** argv) { RegisterConsoleFunctions(console); } + zoneserver_list.Init(); std::unique_ptr server_connection; server_connection.reset(new EQ::Net::ServertalkServer()); diff --git a/world/world_server_command_handler.cpp b/world/world_server_command_handler.cpp index 3a929d89f..9bad28541 100644 --- a/world/world_server_command_handler.cpp +++ b/world/world_server_command_handler.cpp @@ -24,6 +24,7 @@ #include "../common/version.h" #include "worlddb.h" #include "../common/database_schema.h" +#include "../common/database/database_dump_service.h" namespace WorldserverCommandHandler { @@ -51,6 +52,7 @@ namespace WorldserverCommandHandler { function_map["database:version"] = &WorldserverCommandHandler::DatabaseVersion; function_map["database:set-account-status"] = &WorldserverCommandHandler::DatabaseSetAccountStatus; function_map["database:schema"] = &WorldserverCommandHandler::DatabaseGetSchema; + function_map["database:dump"] = &WorldserverCommandHandler::DatabaseDump; EQEmuCommand::HandleMenu(function_map, cmd, argc, argv); } @@ -145,7 +147,7 @@ namespace WorldserverCommandHandler { */ void DatabaseGetSchema(int argc, char **argv, argh::parser &cmd, std::string &description) { - description = "Displays server database schema"; + description = "Displays server database schema"; if (cmd[{"-h", "--help"}]) { return; @@ -202,4 +204,71 @@ namespace WorldserverCommandHandler { std::cout << payload.str() << std::endl; } + /** + * @param argc + * @param argv + * @param cmd + * @param description + */ + void DatabaseDump(int argc, char **argv, argh::parser &cmd, std::string &description) + { + description = "Dumps server database tables"; + + if (cmd[{"-h", "--help"}]) { + return; + } + + std::vector arguments = {}; + std::vector options = { + "--all", + "--content-tables", + "--login-tables", + "--player-tables", + "--state-tables", + "--system-tables", + "--query-serv-tables", + "--table-structure-only", + "--table-lock", + "--dump-path=", + "--dump-output-to-console", + "--drop-table-syntax-only", + "--compress" + }; + + + if (argc < 3) { + EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv); + return; + } + + auto database_dump_service = new DatabaseDumpService(); + bool dump_all = cmd[{"-a", "--all"}]; + + if (!cmd("--dump-path").str().empty()) { + database_dump_service->SetDumpPath(cmd("--dump-path").str()); + } + + /** + * Set Option + */ + database_dump_service->SetDumpContentTables(cmd[{"--content-tables"}] || dump_all); + database_dump_service->SetDumpLoginServerTables(cmd[{"--login-tables"}] || dump_all); + database_dump_service->SetDumpPlayerTables(cmd[{"--player-tables"}] || dump_all); + database_dump_service->SetDumpStateTables(cmd[{"--state-tables"}] || dump_all); + database_dump_service->SetDumpSystemTables(cmd[{"--system-tables"}] || dump_all); + database_dump_service->SetDumpQueryServerTables(cmd[{"--query-serv-tables"}] || dump_all); + database_dump_service->SetDumpAllTables(dump_all); + + database_dump_service->SetDumpWithNoData(cmd[{"--table-structure-only"}]); + database_dump_service->SetDumpTableLock(cmd[{"--table-lock"}]); + database_dump_service->SetDumpWithCompression(cmd[{"--compress"}]); + database_dump_service->SetDumpOutputToConsole(cmd[{"--dump-output-to-console"}]); + database_dump_service->SetDumpDropTableSyntaxOnly(cmd[{"--drop-table-syntax-only"}]); + + /** + * Dump + */ + database_dump_service->Dump(); + } + } \ No newline at end of file diff --git a/world/world_server_command_handler.h b/world/world_server_command_handler.h index f3d4317f2..a32a78f0f 100644 --- a/world/world_server_command_handler.h +++ b/world/world_server_command_handler.h @@ -30,6 +30,7 @@ namespace WorldserverCommandHandler { 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); + void DatabaseDump(int argc, char **argv, argh::parser &cmd, std::string &description); }; diff --git a/world/zonelist.cpp b/world/zonelist.cpp index abb2a5f6f..7f898f89c 100644 --- a/world/zonelist.cpp +++ b/world/zonelist.cpp @@ -39,7 +39,6 @@ ZSList::ZSList() { NextID = 1; CurGroupID = 1; - LastAllocatedPort = 0; memset(pLockedZones, 0, sizeof(pLockedZones)); m_tick.reset(new EQ::Timer(5000, true, std::bind(&ZSList::OnTick, this, std::placeholders::_1))); @@ -76,7 +75,12 @@ void ZSList::Remove(const std::string &uuid) auto iter = zone_server_list.begin(); while (iter != zone_server_list.end()) { if ((*iter)->GetUUID().compare(uuid) == 0) { + auto port = (*iter)->GetCPort(); zone_server_list.erase(iter); + + if (port != 0) { + m_ports_free.push_back(port); + } return; } iter++; @@ -239,6 +243,14 @@ bool ZSList::SetLockedZone(uint16 iZoneID, bool iLock) { return false; } +void ZSList::Init() +{ + const WorldConfig* Config = WorldConfig::get(); + for (uint16 i = Config->ZonePortLow; i <= Config->ZonePortHigh; ++i) { + m_ports_free.push_back(i); + } +} + bool ZSList::IsZoneLocked(uint16 iZoneID) { for (auto &zone : pLockedZones) { if (zone == iZoneID) @@ -577,30 +589,15 @@ void ZSList::RebootZone(const char* ip1, uint16 port, const char* ip2, uint32 sk safe_delete_array(tmp); } -uint16 ZSList::GetAvailableZonePort() +uint16 ZSList::GetAvailableZonePort() { - const WorldConfig *Config = WorldConfig::get(); - int i; - uint16 port = 0; - - if (LastAllocatedPort == 0) - i = Config->ZonePortLow; - else - i = LastAllocatedPort + 1; - - while (i != LastAllocatedPort && port == 0) { - if (i>Config->ZonePortHigh) - i = Config->ZonePortLow; - - if (!FindByPort(i)) { - port = i; - break; - } - i++; + if (m_ports_free.empty()) { + return 0; } - LastAllocatedPort = port; - return port; + auto first = m_ports_free.front(); + m_ports_free.pop_front(); + return first; } uint32 ZSList::TriggerBootup(uint32 iZoneID, uint32 iInstanceID) { diff --git a/world/zonelist.h b/world/zonelist.h index 8b8525f57..5066d4e6d 100644 --- a/world/zonelist.h +++ b/world/zonelist.h @@ -7,6 +7,7 @@ #include "../common/event/timer.h" #include #include +#include class WorldTCPConnection; class ServerPacket; @@ -22,6 +23,7 @@ public: ZSList(); ~ZSList(); + void Init(); bool IsZoneLocked(uint16 iZoneID); bool SendPacket(ServerPacket *pack); bool SendPacket(uint32 zoneid, ServerPacket *pack); @@ -73,8 +75,7 @@ private: uint32 NextID; uint16 pLockedZones[MaxLockedZones]; uint32 CurGroupID; - uint16 LastAllocatedPort; - + std::deque m_ports_free; std::unique_ptr m_tick; std::unique_ptr m_keepalive; diff --git a/zone/aggro.cpp b/zone/aggro.cpp index fac15457e..87b98d599 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -139,7 +139,7 @@ void NPC::DescribeAggro(Client *towho, Mob *mob, bool verbose) { if (RuleB(Aggro, UseLevelAggro)) { - if (GetLevel() < RuleI(Aggro, MinAggroLevel) && mob->GetLevelCon(GetLevel()) == CON_GRAY && GetBodyType() != 3 && !AlwaysAggrosFoes()) + if (GetLevel() < RuleI(Aggro, MinAggroLevel) && mob->GetLevelCon(GetLevel()) == CON_GRAY && GetBodyType() != 3 && !AlwaysAggro()) { towho->Message(Chat::White, "...%s is red to me (basically)", mob->GetName(), dist2, iAggroRange2); return; @@ -147,7 +147,7 @@ void NPC::DescribeAggro(Client *towho, Mob *mob, bool verbose) { } else { - if(GetINT() > RuleI(Aggro, IntAggroThreshold) && mob->GetLevelCon(GetLevel()) == CON_GRAY && !AlwaysAggrosFoes()) { + if(GetINT() > RuleI(Aggro, IntAggroThreshold) && mob->GetLevelCon(GetLevel()) == CON_GRAY && !AlwaysAggro()) { towho->Message(Chat::White, "...%s is red to me (basically)", mob->GetName(), dist2, iAggroRange2); return; @@ -318,7 +318,7 @@ bool Mob::CheckWillAggro(Mob *mob) { //old InZone check taken care of above by !mob->CastToClient()->Connected() ( ( GetLevel() >= RuleI(Aggro, MinAggroLevel)) - ||(GetBodyType() == 3) || AlwaysAggrosFoes() + ||(GetBodyType() == 3) || AlwaysAggro() ||( mob->IsClient() && mob->CastToClient()->IsSitting() ) ||( mob->GetLevelCon(GetLevel()) != CON_GRAY) @@ -352,7 +352,7 @@ bool Mob::CheckWillAggro(Mob *mob) { //old InZone check taken care of above by !mob->CastToClient()->Connected() ( ( GetINT() <= RuleI(Aggro, IntAggroThreshold) ) - || AlwaysAggrosFoes() + || AlwaysAggro() ||( mob->IsClient() && mob->CastToClient()->IsSitting() ) ||( mob->GetLevelCon(GetLevel()) != CON_GRAY) @@ -384,7 +384,7 @@ bool Mob::CheckWillAggro(Mob *mob) { LogAggro("Dist^2: [{}]\n", dist2); LogAggro("Range^2: [{}]\n", iAggroRange2); LogAggro("Faction: [{}]\n", fv); - LogAggro("AlwaysAggroFlag: [{}]\n", AlwaysAggrosFoes()); + LogAggro("AlwaysAggroFlag: [{}]\n", AlwaysAggro()); LogAggro("Int: [{}]\n", GetINT()); LogAggro("Con: [{}]\n", GetLevelCon(mob->GetLevel())); diff --git a/zone/client.cpp b/zone/client.cpp index 8b60b391a..8c4b42957 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -7797,6 +7797,8 @@ FACTION_VALUE Client::GetFactionLevel(uint32 char_id, uint32 npc_id, uint32 p_ra // few optimizations if (GetFeigned()) return FACTION_INDIFFERENT; + if(!zone->CanDoCombat()) + return FACTION_INDIFFERENT; if (invisible_undead && tnpc && !tnpc->SeeInvisibleUndead()) return FACTION_INDIFFERENT; if (IsInvisible(tnpc)) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 7173dd342..1848a926c 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -13327,13 +13327,17 @@ void Client::Handle_OP_Split(const EQApplicationPacket *app) Split_Struct *split = (Split_Struct *)app->pBuffer; //Per the note above, Im not exactly sure what to do on error //to notify the client of the error... - if (!isgrouped) { - Message(Chat::Red, "You can not split money if you're not in a group."); - return; - } - Group *cgroup = GetGroup(); - if (cgroup == nullptr) { - //invalid group, not sure if we should say more... + + Group *group = nullptr; + Raid *raid = nullptr; + + if (IsRaidGrouped()) + raid = GetRaid(); + else if (IsGrouped()) + group = GetGroup(); + + // is there an actual error message for this? + if (raid == nullptr && group == nullptr) { Message(Chat::Red, "You can not split money if you're not in a group."); return; } @@ -13345,7 +13349,11 @@ void Client::Handle_OP_Split(const EQApplicationPacket *app) Message(Chat::Red, "You do not have enough money to do that split."); return; } - cgroup->SplitMoney(split->copper, split->silver, split->gold, split->platinum); + + if (raid) + raid->SplitMoney(raid->GetGroup(this), split->copper, split->silver, split->gold, split->platinum); + else if (group) + group->SplitMoney(split->copper, split->silver, split->gold, split->platinum); return; diff --git a/zone/command.cpp b/zone/command.cpp index 757e2d522..42453faab 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -196,6 +196,7 @@ int command_init(void) command_add("disarmtrap", "Analog for ldon disarm trap for the newer clients since we still don't have it working.", 80, command_disarmtrap) || command_add("distance", "- Reports the distance between you and your target.", 80, command_distance) || command_add("doanim", "[animnum] [type] - Send an EmoteAnim for you or your target", 50, command_doanim) || + command_add("editmassrespawn", "[name-search] [second-value] - Mass (Zone wide) NPC respawn timer editing command", 100, command_editmassrespawn) || command_add("emote", "['name'/'world'/'zone'] [type] [message] - Send an emote message", 80, command_emote) || command_add("emotesearch", "Searches NPC Emotes", 80, command_emotesearch) || command_add("emoteview", "Lists all NPC Emotes", 80, command_emoteview) || @@ -6525,6 +6526,144 @@ void command_doanim(Client *c, const Seperator *sep) c->DoAnim(atoi(sep->arg[1]),atoi(sep->arg[2])); } +void command_editmassrespawn(Client* c, const Seperator* sep) +{ + if (strcasecmp(sep->arg[1], "usage") == 0) { + c->Message(Chat::White, "#editmassrespawn [exact_match: =]npc_type_name new_respawn_seconds (apply)"); + return; + } + + std::string search_npc_type; + if (sep->arg[1]) { + search_npc_type = sep->arg[1]; + } + + int change_respawn_seconds = 0; + if (sep->arg[2] && sep->IsNumber(2)) { + change_respawn_seconds = atoi(sep->arg[2]); + } + + bool change_apply = false; + if (sep->arg[3] && strcasecmp(sep->arg[3], "apply") == 0) { + change_apply = true; + } + + std::string search_encapsulator = "%"; + if (search_npc_type[0] == '=') { + + search_npc_type = search_npc_type.substr(1); + search_encapsulator = ""; + } + + std::string query = fmt::format( + SQL( + SELECT npc_types.id, spawn2.spawngroupID, spawn2.id, npc_types.name, spawn2.respawntime + FROM spawn2 + INNER JOIN spawnentry ON spawn2.spawngroupID = spawnentry.spawngroupID + INNER JOIN npc_types ON spawnentry.npcID = npc_types.id + WHERE spawn2.zone LIKE '{}' + AND spawn2.version = '{}' + AND npc_types.name LIKE '{}{}{}' + ORDER BY npc_types.id, spawn2.spawngroupID, spawn2.id + ), + zone->GetShortName(), + zone->GetInstanceVersion(), + search_encapsulator, + search_npc_type, + search_encapsulator + ); + + std::string status = "(Searching)"; + if (change_apply) { + status = "(Applying)"; + } + + int results_count = 0; + + auto results = database.QueryDatabase(query); + if (results.Success() && results.RowCount()) { + + results_count = results.RowCount(); + + for (auto row : results) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC (npcid:{}) (sgid:{}) (s2id:{}) [{}] Respawn: Current [{}] New [{}] {}", + row[0], + row[1], + row[2], + row[3], + row[4], + change_respawn_seconds, + status + ).c_str() + ); + } + + c->Message(Chat::Yellow, "Found (%i) NPC's that match this search...", results_count); + + if (change_respawn_seconds > 0) { + + if (change_apply) { + + results = database.QueryDatabase( + fmt::format( + SQL( + UPDATE spawn2 + SET respawntime = '{}' + WHERE id IN ( + SELECT spawn2.id + FROM spawn2 + INNER JOIN spawnentry ON spawn2.spawngroupID = spawnentry.spawngroupID + INNER JOIN npc_types ON spawnentry.npcID = npc_types.id + WHERE spawn2.zone LIKE '{}' + AND spawn2.version = '{}' + AND npc_types.name LIKE '{}{}{}' + ) + ), + change_respawn_seconds, + zone->GetShortName(), + zone->GetInstanceVersion(), + search_encapsulator, + search_npc_type, + search_encapsulator + ) + ); + + if (results.Success()) { + + c->Message(Chat::Yellow, "Changes applied to (%i) NPC 'Spawn2' entries", results_count); + zone->Repop(); + } + else { + + c->Message(Chat::Yellow, "Found (0) NPC's that match this search..."); + } + } + else { + + std::string saylink = fmt::format( + "#editmassrespawn {}{} {} apply", + (search_encapsulator.empty() ? "=" : ""), + search_npc_type, + change_respawn_seconds + ); + + c->Message( + Chat::Yellow, "To apply these changes, click <%s> or type [%s]", + EQEmu::SayLinkEngine::GenerateQuestSaylink(saylink, false, "Apply").c_str(), + saylink.c_str() + ); + } + } + } + else { + + c->Message(Chat::Yellow, "Found (0) NPC's that match this search..."); + } +} + void command_randomfeatures(Client *c, const Seperator *sep) { Mob *target=c->GetTarget(); @@ -7782,7 +7921,7 @@ void command_npcemote(Client *c, const Seperator *sep) void command_npceditmass(Client *c, const Seperator *sep) { if (strcasecmp(sep->arg[1], "usage") == 0) { - c->Message(Chat::White, "#npceditmass search_column [exact_match: =]search_value change_column change_value"); + c->Message(Chat::White, "#npceditmass search_column [exact_match: =]search_value change_column change_value (apply)"); return; } @@ -7930,7 +8069,7 @@ void command_npceditmass(Client *c, const Seperator *sep) std::string saylink = fmt::format( "#npceditmass {} {}{} {} {} apply", search_column, - (exact_match ? '=' : '\0'), + (exact_match ? "=" : ""), search_value, change_column, change_value diff --git a/zone/command.h b/zone/command.h index d64950f9c..7c1af0edf 100644 --- a/zone/command.h +++ b/zone/command.h @@ -91,6 +91,7 @@ void command_disablerecipe(Client *c, const Seperator *sep); void command_disarmtrap(Client *c, const Seperator *sep); void command_distance(Client *c, const Seperator *sep); void command_doanim(Client *c, const Seperator *sep); +void command_editmassrespawn(Client* c, const Seperator* sep); void command_emote(Client *c, const Seperator *sep); void command_emotesearch(Client* c, const Seperator *sep); void command_emoteview(Client* c, const Seperator *sep); diff --git a/zone/corpse.cpp b/zone/corpse.cpp index 5858d5acc..fd4174868 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -262,7 +262,7 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob ( 0, // uint8 in_legtexture, 0, // uint8 in_feettexture, 0, // uint8 in_usemodel, - 0 // bool in_always_aggros_foes + 0 // bool in_always_aggro ), corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)), corpse_rez_timer(RuleI(Character, CorpseResTimeMS)), diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 871cc0ced..3ca3184d5 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -822,6 +822,22 @@ XS(XS__isdisctome) { XSRETURN(1); } +XS(XS__getspellname); +XS(XS__getspellname) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getspellname(uint32 spell_id)"); + + dXSTARG; + uint32 spell_id = (int) SvIV(ST(0)); + std::string spell_name = quest_manager.getspellname(spell_id); + + sv_setpv(TARG, spell_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + XS(XS__safemove); XS(XS__safemove) { dXSARGS; @@ -2342,7 +2358,6 @@ XS(XS__updatetaskactivity) { XS(XS__resettaskactivity); XS(XS__resettaskactivity) { dXSARGS; - unsigned int task, activity; if (items == 2) { int task_id = (int) SvIV(ST(0)); int activity_id = (int) SvIV(ST(1)); @@ -2613,6 +2628,23 @@ XS(XS__istaskappropriate) { XSRETURN(1); } +XS(XS__gettaskname); +XS(XS__gettaskname) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: quest::gettaskname(uint32 task_id)"); + } + + dXSTARG; + uint32 task_id = (int) SvIV(ST(0)); + std::string task_name = quest_manager.gettaskname(task_id); + + sv_setpv(TARG, task_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + XS(XS__popup); // prototype to pass -Wmissing-prototypes XS(XS__popup) { dXSARGS; @@ -2797,6 +2829,35 @@ XS(XS__collectitems) { XSRETURN_IV(quantity); } +XS(XS__countitem); +XS(XS__countitem) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::countitem(int item_id)"); + + uint32 item_id = (int) SvIV(ST(0)); + + int quantity = quest_manager.countitem(item_id); + + XSRETURN_IV(quantity); +} + +XS(XS__getitemname); +XS(XS__getitemname) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getitemname(uint32 item_id)"); + + dXSTARG; + uint32 item_id = (int) SvIV(ST(0)); + std::string item_name = quest_manager.getitemname(item_id); + + sv_setpv(TARG, item_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + XS(XS__UpdateSpawnTimer); XS(XS__UpdateSpawnTimer) { dXSARGS; @@ -3063,6 +3124,25 @@ XS(XS__RemoveFromInstanceByCharID) { XSRETURN_EMPTY; } +XS(XS__CheckInstanceByCharID); +XS(XS__CheckInstanceByCharID) { + dXSARGS; + if (items != 2) { + Perl_croak(aTHX_ "Usage: quest::CheckInstanceByCharID(uint16 instance_id, uint32 char_id)"); + } + + bool RETVAL; + dXSTARG; + + uint16 instance_id = (int) SvUV(ST(0)); + uint32 char_id = (int) SvUV(ST(1)); + RETVAL = quest_manager.CheckInstanceByCharID(instance_id, char_id); + XSprePUSH; + PUSHu((IV) RETVAL); + + XSRETURN(1); +} + XS(XS__RemoveAllFromInstance); XS(XS__RemoveAllFromInstance) { dXSARGS; @@ -3169,6 +3249,58 @@ XS(XS__getguildnamebyid) { XSRETURN(1); } +XS(XS__getguildidbycharid); +XS(XS__getguildidbycharid) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getguildidbycharid(uint32 char_id)"); + dXSTARG; + + int RETVAL; + uint32 char_id = (int) SvUV(ST(0)); + + RETVAL = quest_manager.getguildidbycharid(char_id); + + XSprePUSH; + PUSHi((IV)RETVAL); + + XSRETURN(1); +} + +XS(XS__getgroupidbycharid); +XS(XS__getgroupidbycharid) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getgroupidbycharid(uint32 char_id)"); + dXSTARG; + + int RETVAL; + uint32 char_id = (int) SvUV(ST(0)); + + RETVAL = quest_manager.getgroupidbycharid(char_id); + XSprePUSH; + PUSHi((IV)RETVAL); + + XSRETURN(1); +} + +XS(XS__getraididbycharid); +XS(XS__getraididbycharid) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getraididbycharid(uint32 char_id)"); + dXSTARG; + + int RETVAL; + uint32 char_id = (int) SvUV(ST(0)); + + RETVAL = quest_manager.getraididbycharid(char_id); + XSprePUSH; + PUSHi((IV)RETVAL); + + XSRETURN(1); +} + XS(XS__SetRunning); XS(XS__SetRunning) { dXSARGS; @@ -3875,6 +4007,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "RemoveAllFromInstance"), XS__RemoveAllFromInstance, file); newXS(strcpy(buf, "RemoveFromInstance"), XS__RemoveFromInstance, file); newXS(strcpy(buf, "RemoveFromInstanceByCharID"), XS__RemoveFromInstanceByCharID, file); + newXS(strcpy(buf, "CheckInstanceByCharID"), XS__CheckInstanceByCharID, file); newXS(strcpy(buf, "SendMail"), XS__SendMail, file); newXS(strcpy(buf, "SetRunning"), XS__SetRunning, file); newXS(strcpy(buf, "activespeakactivity"), XS__activespeakactivity, file); @@ -3899,6 +4032,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "clearspawntimers"), XS__clearspawntimers, file); newXS(strcpy(buf, "collectitems"), XS__collectitems, file); newXS(strcpy(buf, "completedtasksinset"), XS__completedtasksinset, file); + newXS(strcpy(buf, "countitem"), XS__countitem, file); newXS(strcpy(buf, "createdoor"), XS__CreateDoor, file); newXS(strcpy(buf, "creategroundobject"), XS__CreateGroundObject, file); newXS(strcpy(buf, "creategroundobjectfrommodel"), XS__CreateGroundObjectFromModel, file); @@ -3939,14 +4073,20 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "forcedoorclose"), XS__forcedoorclose, file); newXS(strcpy(buf, "forcedooropen"), XS__forcedooropen, file); newXS(strcpy(buf, "getinventoryslotid"), XS__getinventoryslotid, file); + newXS(strcpy(buf, "getitemname"), XS__getitemname, file); newXS(strcpy(buf, "getItemName"), XS_qc_getItemName, file); newXS(strcpy(buf, "get_spawn_condition"), XS__get_spawn_condition, file); newXS(strcpy(buf, "getguildnamebyid"), XS__getguildnamebyid, file); + newXS(strcpy(buf, "getguildidbycharid"), XS__getguildidbycharid, file); + newXS(strcpy(buf, "getgroupidbycharid"), XS__getgroupidbycharid, file); + newXS(strcpy(buf, "getraididbycharid"), XS__getraididbycharid, file); + newXS(strcpy(buf, "getspellname"), XS__getspellname, file); newXS(strcpy(buf, "getlevel"), XS__getlevel, file); newXS(strcpy(buf, "getplayerburiedcorpsecount"), XS__getplayerburiedcorpsecount, file); newXS(strcpy(buf, "getplayercorpsecount"), XS__getplayercorpsecount, file); newXS(strcpy(buf, "getplayercorpsecountbyzoneid"), XS__getplayercorpsecountbyzoneid, file); newXS(strcpy(buf, "gettaskactivitydonecount"), XS__gettaskactivitydonecount, file); + newXS(strcpy(buf, "gettaskname"), XS__gettaskname, file); newXS(strcpy(buf, "givecash"), XS__givecash, file); newXS(strcpy(buf, "gmmove"), XS__gmmove, file); newXS(strcpy(buf, "gmsay"), XS__gmsay, file); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 0d1086ad1..ddae2dd26 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -393,6 +393,10 @@ bool lua_is_disc_tome(int item_id) { return quest_manager.isdisctome(item_id); } +std::string lua_get_spell_name(uint32 spell_id) { + return quest_manager.getspellname(spell_id); +} + void lua_safe_move() { quest_manager.safemove(); } @@ -729,6 +733,10 @@ bool lua_is_task_appropriate(int task) { return quest_manager.istaskappropriate(task); } +std::string lua_get_task_name(uint32 task_id) { + return quest_manager.gettaskname(task_id); +} + void lua_popup(const char *title, const char *text, uint32 id, uint32 buttons, uint32 duration) { quest_manager.popup(title, text, id, buttons, duration); } @@ -781,6 +789,10 @@ int lua_collect_items(uint32 item_id, bool remove) { return quest_manager.collectitems(item_id, remove); } +int lua_count_item(uint32 item_id) { + return quest_manager.countitem(item_id); +} + void lua_update_spawn_timer(uint32 id, uint32 new_time) { quest_manager.UpdateSpawnTimer(id, new_time); } @@ -803,6 +815,10 @@ std::string lua_item_link(int item_id) { return quest_manager.varlink(text, item_id); } +std::string lua_get_item_name(uint32 item_id) { + return quest_manager.getitemname(item_id); +} + std::string lua_say_link(const char *phrase, bool silent, const char *link_name) { char text[256] = { 0 }; strncpy(text, phrase, 255); @@ -858,6 +874,18 @@ const char *lua_get_guild_name_by_id(uint32 guild_id) { return quest_manager.getguildnamebyid(guild_id); } +int lua_get_guild_id_by_char_id(uint32 char_id) { + return database.GetGuildIDByCharID(char_id); +} + +int lua_get_group_id_by_char_id(uint32 char_id) { + return database.GetGroupIDByCharID(char_id); +} + +int lua_get_raid_id_by_char_id(uint32 char_id) { + return database.GetRaidIDByCharID(char_id); +} + uint32 lua_create_instance(const char *zone, uint32 version, uint32 duration) { return quest_manager.CreateInstance(zone, version, duration); } @@ -910,6 +938,10 @@ void lua_remove_from_instance_by_char_id(uint32 instance_id, uint32 char_id) { quest_manager.RemoveFromInstanceByCharID(instance_id, char_id); } +bool lua_check_instance_by_char_id(uint32 instance_id, uint32 char_id) { + return quest_manager.CheckInstanceByCharID(instance_id, char_id); +} + void lua_remove_all_from_instance(uint32 instance_id) { quest_manager.RemoveAllFromInstance(instance_id); } @@ -1632,6 +1664,7 @@ luabind::scope lua_register_general() { luabind::def("depop_zone", &lua_depop_zone), luabind::def("repop_zone", &lua_repop_zone), luabind::def("is_disc_tome", &lua_is_disc_tome), + luabind::def("get_spell_name", (std::string(*)(uint32))&lua_get_spell_name), luabind::def("safe_move", &lua_safe_move), luabind::def("rain", &lua_rain), luabind::def("snow", &lua_snow), @@ -1699,6 +1732,7 @@ luabind::scope lua_register_general() { luabind::def("active_tasks_in_set", &lua_active_tasks_in_set), luabind::def("completed_tasks_in_set", &lua_completed_tasks_in_set), luabind::def("is_task_appropriate", &lua_is_task_appropriate), + luabind::def("get_task_name", (std::string(*)(uint32))&lua_get_task_name), luabind::def("popup", &lua_popup), luabind::def("clear_spawn_timers", &lua_clear_spawn_timers), luabind::def("zone_emote", &lua_zone_emote), @@ -1712,11 +1746,13 @@ luabind::scope lua_register_general() { luabind::def("create_door", &lua_create_door), luabind::def("modify_npc_stat", &lua_modify_npc_stat), luabind::def("collect_items", &lua_collect_items), + luabind::def("count_item", &lua_count_item), luabind::def("update_spawn_timer", &lua_update_spawn_timer), luabind::def("merchant_set_item", (void(*)(uint32,uint32))&lua_merchant_set_item), luabind::def("merchant_set_item", (void(*)(uint32,uint32,uint32))&lua_merchant_set_item), luabind::def("merchant_count_item", &lua_merchant_count_item), luabind::def("item_link", &lua_item_link), + luabind::def("get_item_name", (std::string(*)(uint32))&lua_get_item_name), luabind::def("say_link", (std::string(*)(const char*,bool,const char*))&lua_say_link), luabind::def("say_link", (std::string(*)(const char*,bool))&lua_say_link), luabind::def("say_link", (std::string(*)(const char*))&lua_say_link), @@ -1728,6 +1764,9 @@ luabind::scope lua_register_general() { luabind::def("set_data", (void(*)(std::string, std::string, std::string))&lua_set_data), luabind::def("delete_data", (bool(*)(std::string))&lua_delete_data), luabind::def("get_guild_name_by_id", &lua_get_guild_name_by_id), + luabind::def("get_guild_id_by_char_id", &lua_get_guild_id_by_char_id), + luabind::def("get_group_id_by_char_id", &lua_get_group_id_by_char_id), + luabind::def("get_raid_id_by_char_id", &lua_get_raid_id_by_char_id), luabind::def("create_instance", &lua_create_instance), luabind::def("destroy_instance", &lua_destroy_instance), luabind::def("update_instance_timer", &lua_update_instance_timer), @@ -1742,6 +1781,7 @@ luabind::scope lua_register_general() { luabind::def("assign_raid_to_instance", &lua_assign_raid_to_instance), luabind::def("remove_from_instance", &lua_remove_from_instance), luabind::def("remove_from_instance_by_char_id", &lua_remove_from_instance_by_char_id), + luabind::def("check_instance_by_char_id", (bool(*)(uint16, uint32))&lua_check_instance_by_char_id), luabind::def("remove_all_from_instance", &lua_remove_all_from_instance), luabind::def("flag_instance_by_group_leader", &lua_flag_instance_by_group_leader), luabind::def("flag_instance_by_raid_leader", &lua_flag_instance_by_raid_leader), diff --git a/zone/lua_raid.cpp b/zone/lua_raid.cpp index 93c1f4c28..c181b8bd2 100644 --- a/zone/lua_raid.cpp +++ b/zone/lua_raid.cpp @@ -52,14 +52,14 @@ uint32 Lua_Raid::GetTotalRaidDamage(Lua_Mob other) { return self->GetTotalRaidDamage(other); } -void Lua_Raid::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum) { +void Lua_Raid::SplitMoney(uint32 gid, uint32 copper, uint32 silver, uint32 gold, uint32 platinum) { Lua_Safe_Call_Void(); - self->SplitMoney(copper, silver, gold, platinum); + self->SplitMoney(gid, copper, silver, gold, platinum); } -void Lua_Raid::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Lua_Client splitter) { +void Lua_Raid::SplitMoney(uint32 gid, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Lua_Client splitter) { Lua_Safe_Call_Void(); - self->SplitMoney(copper, silver, gold, platinum, splitter); + self->SplitMoney(gid, copper, silver, gold, platinum, splitter); } void Lua_Raid::BalanceHP(int penalty, uint32 group_id) { @@ -146,8 +146,8 @@ luabind::scope lua_register_raid() { .def("GetGroup", (int(Lua_Raid::*)(Lua_Client))&Lua_Raid::GetGroup) .def("SplitExp", (void(Lua_Raid::*)(uint32,Lua_Mob))&Lua_Raid::SplitExp) .def("GetTotalRaidDamage", (uint32(Lua_Raid::*)(Lua_Mob))&Lua_Raid::GetTotalRaidDamage) - .def("SplitMoney", (void(Lua_Raid::*)(uint32,uint32,uint32,uint32))&Lua_Raid::SplitMoney) - .def("SplitMoney", (void(Lua_Raid::*)(uint32,uint32,uint32,uint32,Lua_Client))&Lua_Raid::SplitMoney) + .def("SplitMoney", (void(Lua_Raid::*)(uint32,uint32,uint32,uint32,uint32))&Lua_Raid::SplitMoney) + .def("SplitMoney", (void(Lua_Raid::*)(uint32,uint32,uint32,uint32,uint32,Lua_Client))&Lua_Raid::SplitMoney) .def("BalanceHP", (void(Lua_Raid::*)(int,uint32))&Lua_Raid::BalanceHP) .def("IsLeader", (bool(Lua_Raid::*)(const char*))&Lua_Raid::IsLeader) .def("IsGroupLeader", (bool(Lua_Raid::*)(const char*))&Lua_Raid::IsGroupLeader) diff --git a/zone/lua_raid.h b/zone/lua_raid.h index 626b98bb2..bc10a729c 100644 --- a/zone/lua_raid.h +++ b/zone/lua_raid.h @@ -34,8 +34,8 @@ public: int GetGroup(Lua_Client c); void SplitExp(uint32 exp, Lua_Mob other); uint32 GetTotalRaidDamage(Lua_Mob other); - void SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum); - void SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Lua_Client splitter); + void SplitMoney(uint32 gid, uint32 copper, uint32 silver, uint32 gold, uint32 platinum); + void SplitMoney(uint32 gid, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Lua_Client splitter); void BalanceHP(int penalty, uint32 group_id); bool IsLeader(const char *c); bool IsLeader(Lua_Client c); diff --git a/zone/main.cpp b/zone/main.cpp index 6d3eee96c..0445534d4 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -92,7 +92,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/unix.h" #endif -volatile bool RunLoops = true; extern volatile bool is_zone_loaded; EntityList entity_list; @@ -577,19 +576,19 @@ int main(int argc, char** argv) { return 0; } +void Shutdown() +{ + Zone::Shutdown(true); + LogInfo("Shutting down..."); + LogSys.CloseFileLogs(); + EQ::EventLoop::Get().Shutdown(); +} + void CatchSignal(int sig_num) { #ifdef _WINDOWS LogInfo("Recieved signal: [{}]", sig_num); #endif - RunLoops = false; -} - -void Shutdown() -{ - Zone::Shutdown(true); - RunLoops = false; - LogInfo("Shutting down..."); - LogSys.CloseFileLogs(); + Shutdown(); } /* Update Window Title with relevant information */ diff --git a/zone/mob.cpp b/zone/mob.cpp index 2e3d3d290..5d25c42f0 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -94,7 +94,7 @@ Mob::Mob( uint8 in_legtexture, uint8 in_feettexture, uint16 in_usemodel, - bool in_always_aggros_foes + bool in_always_aggro ) : attack_timer(2000), attack_dw_timer(2000), @@ -276,7 +276,7 @@ Mob::Mob( qglobal = 0; spawned = false; rare_spawn = false; - always_aggros_foes = in_always_aggros_foes; + always_aggro = in_always_aggro; InitializeBuffSlots(); diff --git a/zone/mob.h b/zone/mob.h index 2a89ba384..f1c128080 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -579,7 +579,7 @@ public: inline const GravityBehavior GetFlyMode() const { return flymode; } bool IsBoat() const; bool IsControllableBoat() const; - inline const bool AlwaysAggrosFoes() const { return always_aggros_foes; } + inline const bool AlwaysAggro() const { return always_aggro; } //Group virtual bool HasRaid() = 0; @@ -1391,7 +1391,7 @@ protected: Timer ranged_timer; float attack_speed; //% increase/decrease in attack speed (not haste) int attack_delay; //delay between attacks in 10ths of seconds - bool always_aggros_foes; + bool always_aggro; int16 slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%) Timer tic_timer; Timer mana_timer; diff --git a/zone/npc.cpp b/zone/npc.cpp index 307e7a926..b7dc3272a 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -114,7 +114,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi npc_type_data->legtexture, npc_type_data->feettexture, npc_type_data->use_model, - npc_type_data->always_aggros_foes + npc_type_data->always_aggro ), attacked_timer(CombatEventTimer_expire), swarm_timer(100), @@ -962,7 +962,7 @@ void NPC::Depop(bool StartSpawnTimer) { } bool NPC::DatabaseCastAccepted(int spell_id) { - for (int i=0; i < 12; i++) { + for (int i=0; i < EFFECT_COUNT; i++) { switch(spells[spell_id].effectid[i]) { case SE_Stamina: { if(IsEngaged() && GetHPRatio() < 100) @@ -3093,6 +3093,14 @@ bool NPC::AICheckCloseBeneficialSpells( continue; } + if (!mob->CheckLosFN(caster)) { + continue; + } + + if (mob->GetReverseFactionCon(caster) >= FACTION_KINDLY) { + continue; + } + LogAICastBeneficialClose( "NPC [{}] Distance [{}] Cast Range [{}] Caster [{}]", mob->GetCleanName(), @@ -3101,10 +3109,6 @@ bool NPC::AICheckCloseBeneficialSpells( caster->GetCleanName() ); - if (mob->GetReverseFactionCon(caster) >= FACTION_KINDLY) { - continue; - } - if ((spell_types & SpellType_Buff) && !RuleB(NPC, BuffFriends)) { if (mob != caster) { spell_types = SpellType_Heal; diff --git a/zone/perl_raids.cpp b/zone/perl_raids.cpp index 72672de4e..670d445e5 100644 --- a/zone/perl_raids.cpp +++ b/zone/perl_raids.cpp @@ -247,13 +247,14 @@ XS(XS_Raid_SplitMoney); /* prototype to pass -Wmissing-prototypes */ XS(XS_Raid_SplitMoney) { dXSARGS; if (items != 5) - Perl_croak(aTHX_ "Usage: Raid::SplitMoney(THIS, uint32 copper, uint32 silver, uint32 gold, uint32 platinum)"); + Perl_croak(aTHX_ "Usage: Raid::SplitMoney(THIS, uint32 gid, uint32 copper, uint32 silver, uint32 gold, uint32 platinum)"); { Raid *THIS; - uint32 copper = (uint32) SvUV(ST(1)); - uint32 silver = (uint32) SvUV(ST(2)); - uint32 gold = (uint32) SvUV(ST(3)); - uint32 platinum = (uint32) SvUV(ST(4)); + uint32 gid = (uint32) SvUV(ST(1)); + uint32 copper = (uint32) SvUV(ST(2)); + uint32 silver = (uint32) SvUV(ST(3)); + uint32 gold = (uint32) SvUV(ST(4)); + uint32 platinum = (uint32) SvUV(ST(5)); if (sv_derived_from(ST(0), "Raid")) { IV tmp = SvIV((SV *) SvRV(ST(0))); @@ -263,7 +264,7 @@ XS(XS_Raid_SplitMoney) { if (THIS == nullptr) Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); - THIS->SplitMoney(copper, silver, gold, platinum); + THIS->SplitMoney(gid, copper, silver, gold, platinum); } XSRETURN_EMPTY; } @@ -569,7 +570,7 @@ XS(boot_Raid) { newXSproto(strcpy(buf, "GetGroup"), XS_Raid_GetGroup, file, "$$"); newXSproto(strcpy(buf, "SplitExp"), XS_Raid_SplitExp, file, "$$$"); newXSproto(strcpy(buf, "GetTotalRaidDamage"), XS_Raid_GetTotalRaidDamage, file, "$$"); - newXSproto(strcpy(buf, "SplitMoney"), XS_Raid_SplitMoney, file, "$$$$$"); + newXSproto(strcpy(buf, "SplitMoney"), XS_Raid_SplitMoney, file, "$$$$$$"); newXSproto(strcpy(buf, "BalanceHP"), XS_Raid_BalanceHP, file, "$$$"); newXSproto(strcpy(buf, "IsLeader"), XS_Raid_IsLeader, file, "$$"); newXSproto(strcpy(buf, "IsGroupLeader"), XS_Raid_IsGroupLeader, file, "$$"); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index bea136e28..62cd73610 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -906,6 +906,15 @@ bool QuestManager::isdisctome(int item_id) { return(true); } +std::string QuestManager::getspellname(uint32 spell_id) { + if (!IsValidSpell(spell_id)) { + return "INVALID SPELL ID IN GETSPELLNAME"; + } + + std::string spell_name = GetSpellName(spell_id); + return spell_name; +} + void QuestManager::safemove() { QuestManagerCurrentQuestVars(); if (initiator && initiator->IsClient()) @@ -2432,6 +2441,16 @@ bool QuestManager::istaskappropriate(int task) { return false; } +std::string QuestManager::gettaskname(uint32 task_id) { + QuestManagerCurrentQuestVars(); + + if (RuleB(TaskSystem, EnableTaskSystem)) { + return taskmanager->GetTaskName(task_id); + } + + return std::string(); +} + void QuestManager::clearspawntimers() { if(!zone) return; @@ -2580,6 +2599,32 @@ int QuestManager::collectitems(uint32 item_id, bool remove) return quantity; } +int QuestManager::countitem(uint32 item_id) { + QuestManagerCurrentQuestVars(); + int quantity = 0; + EQEmu::ItemInstance *item = nullptr; + static const int16 slots[][2] = { + { EQEmu::invslot::POSSESSIONS_BEGIN, EQEmu::invslot::POSSESSIONS_END }, + { EQEmu::invbag::GENERAL_BAGS_BEGIN, EQEmu::invbag::GENERAL_BAGS_END }, + { EQEmu::invbag::CURSOR_BAG_BEGIN, EQEmu::invbag::CURSOR_BAG_END}, + { EQEmu::invslot::BANK_BEGIN, EQEmu::invslot::BANK_END }, + { EQEmu::invbag::BANK_BAGS_BEGIN, EQEmu::invbag::BANK_BAGS_END }, + { EQEmu::invslot::SHARED_BANK_BEGIN, EQEmu::invslot::SHARED_BANK_END }, + { EQEmu::invbag::SHARED_BANK_BAGS_BEGIN, EQEmu::invbag::SHARED_BANK_BAGS_END }, + }; + const size_t size = sizeof(slots) / sizeof(slots[0]); + for (int slot_index = 0; slot_index < size; ++slot_index) { + for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) { + item = initiator->GetInv().GetItem(slot_id); + if (item && item->GetID() == item_id) { + quantity += item->IsStackable() ? item->GetCharges() : 1; + } + } + } + + return quantity; +} + void QuestManager::UpdateSpawnTimer(uint32 id, uint32 newTime) { bool found = false; @@ -2670,6 +2715,16 @@ const char* QuestManager::varlink(char* perltext, int item_id) { return perltext; } +std::string QuestManager::getitemname(uint32 item_id) { + const EQEmu::ItemData* item_data = database.GetItem(item_id); + if (!item_data) { + return "INVALID ITEM ID IN GETITEMNAME"; + } + + std::string item_name = item_data->Name; + return item_name; +} + uint16 QuestManager::CreateInstance(const char *zone, int16 version, uint32 duration) { QuestManagerCurrentQuestVars(); @@ -2811,6 +2866,10 @@ void QuestManager::RemoveFromInstanceByCharID(uint16 instance_id, uint32 char_id database.RemoveClientFromInstance(instance_id, char_id); } +bool QuestManager::CheckInstanceByCharID(uint16 instance_id, uint32 char_id) { + return database.CharacterInInstanceGroup(instance_id, char_id); +} + void QuestManager::RemoveAllFromInstance(uint16 instance_id) { QuestManagerCurrentQuestVars(); @@ -2876,6 +2935,27 @@ const char* QuestManager::getguildnamebyid(int guild_id) { return(""); } +int QuestManager::getguildidbycharid(uint32 char_id) { + if (char_id > 0) { + return database.GetGuildIDByCharID(char_id); + } + return 0; +} + +int QuestManager::getgroupidbycharid(uint32 char_id) { + if (char_id > 0) { + return database.GetGroupIDByCharID(char_id); + } + return 0; +} + +int QuestManager::getraididbycharid(uint32 char_id) { + if (char_id > 0) { + return database.GetRaidIDByCharID(char_id); + } + return 0; +} + void QuestManager::SetRunning(bool val) { QuestManagerCurrentQuestVars(); diff --git a/zone/questmgr.h b/zone/questmgr.h index 5ad551c43..c47c775e7 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -107,6 +107,7 @@ public: void level(int newlevel); void traindisc(int discipline_tome_item_id); bool isdisctome(int item_id); + std::string getspellname(uint32 spell_id); void safemove(); void rain(int weather); void snow(int weather); @@ -213,12 +214,15 @@ public: int activetasksinset(int taskset); int completedtasksinset(int taskset); bool istaskappropriate(int task); + std::string gettaskname(uint32 task_id); void clearspawntimers(); void ze(int type, const char *str); void we(int type, const char *str); int getlevel(uint8 type); int collectitems(uint32 item_id, bool remove); int collectitems_processSlot(int16 slot_id, uint32 item_id, bool remove); + int countitem(uint32 item_id); + std::string getitemname(uint32 item_id); void enabletitle(int titleset); bool checktitle(int titlecheck); void removetitle(int titlecheck); @@ -242,6 +246,7 @@ public: void AssignRaidToInstance(uint16 instance_id); void RemoveFromInstance(uint16 instance_id); void RemoveFromInstanceByCharID(uint16 instance_id, uint32 char_id); + bool CheckInstanceByCharID(uint16 instance_id, uint32 char_id); //void RemoveGroupFromInstance(uint16 instance_id); //potentially useful but not implmented at this time. //void RemoveRaidFromInstance(uint16 instance_id); //potentially useful but not implmented at this time. void RemoveAllFromInstance(uint16 instance_id); @@ -251,6 +256,9 @@ public: const char* varlink(char* perltext, int item_id); std::string saylink(char *saylink_text, bool silent, const char *link_name); const char* getguildnamebyid(int guild_id); + int getguildidbycharid(uint32 char_id); + int getgroupidbycharid(uint32 char_id); + int getraididbycharid(uint32 char_id); void SetRunning(bool val); bool IsRunning(); void FlyMode(GravityBehavior flymode); diff --git a/zone/raids.cpp b/zone/raids.cpp index 7a153864d..a4e1535d3 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -728,15 +728,20 @@ void Raid::BalanceMana(int32 penalty, uint32 gid, float range, Mob* caster, int3 } //basically the same as Group's version just with more people like a lot of non group specific raid stuff -void Raid::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter){ +//this only functions if the member has a group in the raid. This does not support /autosplit? +void Raid::SplitMoney(uint32 gid, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter) +{ //avoid unneeded work + if (gid == RAID_GROUPLESS) + return; + if(copper == 0 && silver == 0 && gold == 0 && platinum == 0) return; uint32 i; uint8 membercount = 0; for (i = 0; i < MAX_RAID_MEMBERS; i++) { - if (members[i].member != nullptr) { + if (members[i].member != nullptr && members[i].GroupNumber == gid) { membercount++; } } @@ -809,11 +814,11 @@ void Raid::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum msg += " as your split"; for (i = 0; i < MAX_RAID_MEMBERS; i++) { - if (members[i].member != nullptr) { // If Group Member is Client - //I could not get MoneyOnCorpse to work, so we use this - members[i].member->AddMoneyToPP(cpsplit, spsplit, gpsplit, ppsplit, true); + if (members[i].member != nullptr && members[i].GroupNumber == gid) { // If Group Member is Client + //I could not get MoneyOnCorpse to work, so we use this + members[i].member->AddMoneyToPP(cpsplit, spsplit, gpsplit, ppsplit, true); - members[i].member->Message(Chat::Green, msg.c_str()); + members[i].member->Message(Chat::Green, msg.c_str()); } } } diff --git a/zone/raids.h b/zone/raids.h index 260ae6c71..26ba90a02 100644 --- a/zone/raids.h +++ b/zone/raids.h @@ -159,7 +159,7 @@ public: void BalanceHP(int32 penalty, uint32 gid, float range = 0, Mob* caster = nullptr, int32 limit = 0); void BalanceMana(int32 penalty, uint32 gid, float range = 0, Mob* caster = nullptr, int32 limit = 0); void HealGroup(uint32 heal_amt, Mob* caster, uint32 gid, float range = 0); - void SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter = nullptr); + void SplitMoney(uint32 gid, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, Client *splitter = nullptr); void GroupBardPulse(Mob* caster, uint16 spellid, uint32 gid); void TeleportGroup(Mob* sender, uint32 zoneID, uint16 instance_id, float x, float y, float z, float heading, uint32 gid); diff --git a/zone/spells.cpp b/zone/spells.cpp index 5b8ee33f8..be1419caa 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3426,6 +3426,8 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r bool isproc, int level_override) { + bool is_damage_or_lifetap_spell = IsDamageSpell(spell_id) || IsLifetapSpell(spell_id); + // well we can't cast a spell on target without a target if(!spelltar) { @@ -3967,9 +3969,6 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r // send to people in the area, ignoring caster and target //live dosent send this to anybody but the caster //entity_list.QueueCloseClients(spelltar, action_packet, true, 200, this, true, spelltar->IsClient() ? FILTER_PCSPELLS : FILTER_NPCSPELLS); - - // TEMPORARY - this is the message for the spell. - // double message on effects that use ChangeHP - working on this message_packet = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct)); CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer; cd->target = action->target; @@ -3980,7 +3979,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r cd->hit_heading = action->hit_heading; cd->hit_pitch = action->hit_pitch; cd->damage = 0; - if(!IsEffectInSpell(spell_id, SE_BindAffinity)){ + if(!IsEffectInSpell(spell_id, SE_BindAffinity) && !is_damage_or_lifetap_spell){ entity_list.QueueCloseClients( spelltar, /* Sender */ message_packet, /* Packet */ @@ -3990,6 +3989,19 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r true, /* Packet ACK */ (spelltar->IsClient() ? FilterPCSpells : FilterNPCSpells) /* Message Filter Type: (8 or 9) */ ); + } else if (is_damage_or_lifetap_spell && + (IsClient() || + (HasOwner() && + GetOwner()->IsClient() + ) + ) + ) { + (HasOwner() ? GetOwner() : this)->CastToClient()->QueuePacket( + message_packet, + true, + Mob::CLIENT_CONNECTINGALL, + (spelltar->IsClient() ? FilterPCSpells : FilterNPCSpells) + ); } safe_delete(action_packet); safe_delete(message_packet); diff --git a/zone/tasks.cpp b/zone/tasks.cpp index 778a556e3..afa7b2062 100644 --- a/zone/tasks.cpp +++ b/zone/tasks.cpp @@ -955,6 +955,17 @@ bool TaskManager::AppropriateLevel(int TaskID, int PlayerLevel) { } +std::string TaskManager::GetTaskName(uint32 task_id) +{ + if (task_id > 0 && task_id < MAXTASKS) { + if (Tasks[task_id] != nullptr) { + return Tasks[task_id]->Title; + } + } + + return std::string(); +} + int TaskManager::GetTaskMinLevel(int TaskID) { if (Tasks[TaskID]->MinLevel) diff --git a/zone/tasks.h b/zone/tasks.h index 0bc45c146..48fc8e2cc 100644 --- a/zone/tasks.h +++ b/zone/tasks.h @@ -299,6 +299,7 @@ public: bool AppropriateLevel(int TaskID, int PlayerLevel); int GetTaskMinLevel(int TaskID); int GetTaskMaxLevel(int TaskID); + std::string GetTaskName(uint32 task_id); void TaskSetSelector(Client *c, ClientTaskState *state, Mob *mob, int TaskSetID); void TaskQuestSetSelector(Client *c, ClientTaskState *state, Mob *mob, int count, int *tasks); // task list provided by QuestManager (perl/lua) void SendActiveTasksToClient(Client *c, bool TaskComplete=false); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 60f770c92..87cfb13ef 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -57,7 +57,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA extern EntityList entity_list; extern Zone* zone; extern volatile bool is_zone_loaded; -extern void CatchSignal(int); +extern void Shutdown(); extern WorldServer worldserver; extern PetitionList petition_list; extern uint32 numclients; @@ -192,8 +192,15 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) if (pack->size != sizeof(ServerConnectInfo)) break; ServerConnectInfo* sci = (ServerConnectInfo*)pack->pBuffer; - LogInfo("World assigned Port: [{}] for this zone", sci->port); - ZoneConfig::SetZonePort(sci->port); + + if (sci->port == 0) { + LogCritical("World did not have a port to assign from this server, the port range was not large enough."); + Shutdown(); + } + else { + LogInfo("World assigned Port: [{}] for this zone", sci->port); + ZoneConfig::SetZonePort(sci->port); + } break; } case ServerOP_ChannelMessage: { @@ -482,7 +489,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } case ServerOP_ShutdownAll: { entity_list.Save(); - CatchSignal(2); + Shutdown(); break; } case ServerOP_ZoneShutdown: { diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 3b03d2563..1dfbeb4a2 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -2507,7 +2507,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load "npc_types.stuck_behavior, " "npc_types.model, " "npc_types.flymode, " - "npc_types.always_aggros_foes " + "npc_types.always_aggro " "FROM npc_types %s", where_condition.c_str() ); @@ -2709,7 +2709,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load temp_npctype_data->stuck_behavior = atoi(row[109]); temp_npctype_data->use_model = atoi(row[110]); temp_npctype_data->flymode = atoi(row[111]); - temp_npctype_data->always_aggros_foes = atoi(row[112]); + temp_npctype_data->always_aggro = atoi(row[112]); temp_npctype_data->skip_auto_scale = false; // hardcoded here for now @@ -3703,7 +3703,7 @@ void ZoneDatabase::LoadBuffs(Client *client) if (!IsValidSpell(buffs[index].spellid)) continue; - for (int effectIndex = 0; effectIndex < 12; ++effectIndex) { + for (int effectIndex = 0; effectIndex < EFFECT_COUNT; ++effectIndex) { if (spells[buffs[index].spellid].effectid[effectIndex] == SE_Charm) { buffs[index].spellid = SPELL_UNKNOWN; diff --git a/zone/zonedump.h b/zone/zonedump.h index 80508f233..0a1bcd07d 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -147,7 +147,7 @@ struct NPCType int8 stuck_behavior; uint16 use_model; int8 flymode; - bool always_aggros_foes; + bool always_aggro; }; namespace player_lootitem {