diff --git a/.gitignore b/.gitignore index 287c07412..1db525804 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,20 @@ 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 +/x64 +/client_files/**/CMakeFiles/ diff --git a/README.md b/README.md index f3b829732..1b08d54be 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) | *** @@ -52,7 +52,7 @@ forum, although pull requests will be much quicker and easier on all parties. ## Resources - [EQEmulator Forums](http://www.eqemulator.org/forums) -- [EQEmulator Wiki](https://github.com/EQEmu/Server/wiki) +- [EQEmulator Wiki](https://eqemu.gitbook.io/) ## Related Repositories * [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests) 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..1935406a2 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -925,6 +925,38 @@ void Database::GetCharName(uint32 char_id, char* name) { } } +const char* Database::GetCharNameByID(uint32 char_id) { + std::string query = fmt::format("SELECT `name` FROM `character_data` WHERE id = {}", char_id); + auto results = QueryDatabase(query); + + if (!results.Success()) { + return ""; + } + + if (results.RowCount() == 0) { + return ""; + } + + auto row = results.begin(); + return row[0]; +} + +const char* Database::GetNPCNameByID(uint32 npc_id) { + std::string query = fmt::format("SELECT `name` FROM `npc_types` WHERE id = {}", npc_id); + auto results = QueryDatabase(query); + + if (!results.Success()) { + return ""; + } + + if (results.RowCount() == 0) { + return ""; + } + + auto row = results.begin(); + return row[0]; +} + bool Database::LoadVariables() { auto results = QueryDatabase(StringFormat("SELECT varname, value, unix_timestamp() FROM variables where unix_timestamp(ts) >= %d", varcache.last_update)); @@ -2158,6 +2190,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 */ @@ -2335,3 +2405,4 @@ int Database::GetInstanceID(uint32 char_id, uint32 zone_id) { return 0; } + diff --git a/common/database.h b/common/database.h index f569da20b..962349fa9 100644 --- a/common/database.h +++ b/common/database.h @@ -133,9 +133,13 @@ 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); + const char *GetCharNameByID(uint32 char_id); + const char *GetNPCNameByID(uint32 npc_id); void LoginIP(uint32 AccountID, const char* LoginIP); /* Instancing */ @@ -192,19 +196,19 @@ public: void GetAccountFromID(uint32 id, char* oAccountName, int16* oStatus); void SetAgreementFlag(uint32 acctid); - + int GetIPExemption(std::string account_ip); int GetInstanceID(uint32 char_id, uint32 zone_id); /* Groups */ - + char* GetGroupLeaderForLogin(const char* name,char* leaderbuf); char* GetGroupLeadershipInfo(uint32 gid, char* leaderbuf, char* maintank = nullptr, char* assist = nullptr, char* puller = nullptr, char *marknpc = nullptr, char *mentoree = nullptr, int *mentor_percent = nullptr, GroupLeadershipAA_Struct* GLAA = nullptr); - + uint32 GetGroupID(const char* name); - + void ClearGroup(uint32 gid = 0); void ClearGroupLeader(uint32 gid = 0); void SetGroupID(const char* name, uint32 id, uint32 charid, uint32 ismerc = false); 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_instances.cpp b/common/database_instances.cpp index 548a84bdb..617a2baf6 100644 --- a/common/database_instances.cpp +++ b/common/database_instances.cpp @@ -97,42 +97,53 @@ bool Database::CheckInstanceExists(uint16 instance_id) { bool Database::CheckInstanceExpired(uint16 instance_id) { - int32 start_time = 0; - int32 duration = 0; + int32 start_time = 0; + int32 duration = 0; uint32 never_expires = 0; - std::string query = StringFormat("SELECT start_time, duration, never_expires FROM instance_list WHERE id=%u", instance_id); + std::string query = StringFormat( + "SELECT start_time, duration, never_expires FROM instance_list WHERE id=%u", + instance_id + ); + auto results = QueryDatabase(query); - if (!results.Success()) + if (!results.Success()) { return true; + } - if (results.RowCount() == 0) + if (results.RowCount() == 0) { return true; + } auto row = results.begin(); - start_time = atoi(row[0]); - duration = atoi(row[1]); + start_time = atoi(row[0]); + duration = atoi(row[1]); never_expires = atoi(row[2]); - if (never_expires == 1) + if (never_expires == 1) { return false; + } - timeval tv; + timeval tv{}; gettimeofday(&tv, nullptr); - if ((start_time + duration) <= tv.tv_sec) - return true; + return (start_time + duration) <= tv.tv_sec; - return false; } bool Database::CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version, uint32 duration) { - std::string query = StringFormat("INSERT INTO instance_list (id, zone, version, start_time, duration)" - " values(%lu, %lu, %lu, UNIX_TIMESTAMP(), %lu)", - (unsigned long)instance_id, (unsigned long)zone_id, (unsigned long)version, (unsigned long)duration); + std::string query = StringFormat( + "INSERT INTO instance_list (id, zone, version, start_time, duration)" + " values (%u, %u, %u, UNIX_TIMESTAMP(), %u)", + instance_id, + zone_id, + version, + duration + ); + auto results = QueryDatabase(query); return results.Success(); @@ -140,66 +151,84 @@ bool Database::CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version bool Database::GetUnusedInstanceID(uint16 &instance_id) { - uint32 count = RuleI(Zone, ReservedInstances); - uint32 max = 65535; + uint32 max_reserved_instance_id = RuleI(Instances, ReservedInstances); + uint32 max = 32000; + + std::string query = StringFormat( + "SELECT IFNULL(MAX(id),%u)+1 FROM instance_list WHERE id > %u", + max_reserved_instance_id, + max_reserved_instance_id + ); + + if (RuleB(Instances, RecycleInstanceIds)) { + query = ( + SQL( + SELECT i.id + 1 AS next_available + FROM instance_list i + LEFT JOIN instance_list i2 ON i2.id = i.id + 1 + WHERE i2.id IS NULL + ORDER BY i.id + LIMIT 0, 1; + + ) + ); + } - std::string query = StringFormat("SELECT IFNULL(MAX(id),%u)+1 FROM instance_list WHERE id > %u", count, count); auto results = QueryDatabase(query); - if (!results.Success()) - { + if (!results.Success()) { instance_id = 0; return false; } - if (results.RowCount() == 0) - { - instance_id = 0; - return false; + if (results.RowCount() == 0) { + instance_id = max_reserved_instance_id; + return true; } auto row = results.begin(); - if (atoi(row[0]) <= max) - { + if (atoi(row[0]) <= max) { instance_id = atoi(row[0]); + return true; } - query = StringFormat("SELECT id FROM instance_list where id > %u ORDER BY id", count); + if (instance_id < max_reserved_instance_id) { + instance_id = max_reserved_instance_id; + return true; + } + + query = StringFormat("SELECT id FROM instance_list where id > %u ORDER BY id", max_reserved_instance_id); results = QueryDatabase(query); - if (!results.Success()) - { + if (!results.Success()) { instance_id = 0; return false; } - if (results.RowCount() == 0) - { + if (results.RowCount() == 0) { instance_id = 0; return false; } - count++; - for (auto row = results.begin(); row != results.end(); ++row) - { - if (count < atoi(row[0])) - { - instance_id = count; + max_reserved_instance_id++; + for (auto row = results.begin(); row != results.end(); ++row) { + if (max_reserved_instance_id < atoi(row[0])) { + instance_id = max_reserved_instance_id; return true; } - if (count > max) - { + if (max_reserved_instance_id > max) { instance_id = 0; return false; } - count++; + max_reserved_instance_id++; } - instance_id = count; + instance_id = max_reserved_instance_id; + return true; } @@ -486,8 +515,7 @@ void Database::BuryCorpsesInInstance(uint16 instance_id) { void Database::DeleteInstance(uint16 instance_id) { - std::string query = StringFormat("DELETE FROM instance_list WHERE id=%u", instance_id); - QueryDatabase(query); + std::string query; query = StringFormat("DELETE FROM instance_list_player WHERE id=%u", instance_id); QueryDatabase(query); @@ -548,17 +576,36 @@ void Database::GetCharactersInInstance(uint16 instance_id, std::list &ch void Database::PurgeExpiredInstances() { - std::string query("SELECT id FROM instance_list where (start_time+duration) <= UNIX_TIMESTAMP() and never_expires = 0"); + + /** + * Delay purging by a day so that we can continue using adjacent free instance id's + * from the table without risking the chance we immediately re-allocate a zone that freshly expired but + * has not been fully de-allocated + */ + std::string query = + SQL( + SELECT + id + FROM + instance_list + where + (start_time + duration) <= (UNIX_TIMESTAMP() - 86400) + and never_expires = 0 + ); + auto results = QueryDatabase(query); - if (!results.Success()) + if (!results.Success()) { return; + } - if (results.RowCount() == 0) + if (results.RowCount() == 0) { return; + } - for (auto row = results.begin(); row != results.end(); ++row) + for (auto row = results.begin(); row != results.end(); ++row) { DeleteInstance(atoi(row[0])); + } } void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration) @@ -566,4 +613,4 @@ void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration) std::string query = StringFormat("UPDATE `instance_list` SET start_time=UNIX_TIMESTAMP(), " "duration=%u WHERE id=%u", new_duration, instance_id); auto results = QueryDatabase(query); -} \ No newline at end of file +} 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/net/daybreak_connection.cpp b/common/net/daybreak_connection.cpp index bd0c39489..693a47bf1 100644 --- a/common/net/daybreak_connection.cpp +++ b/common/net/daybreak_connection.cpp @@ -605,6 +605,8 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) ProcessDecodedPacket(StaticPacket(current, subpacket_length)); current += subpacket_length; } + + break; } case OP_SessionRequest: diff --git a/common/ruletypes.h b/common/ruletypes.h index f1d00529f..2f5b9f246 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -161,6 +161,7 @@ RULE_BOOL(Character, DismountWater, true, "Dismount horses when entering water") RULE_BOOL(Character, UseNoJunkFishing, false, "Disregards junk items when fishing") RULE_BOOL(Character, SoftDeletes, true, "When characters are deleted in character select, they are only soft deleted") RULE_INT(Character, DefaultGuild, 0, "If not 0, new characters placed into the guild # indicated") +RULE_BOOL(Character, ProcessFearedProximity, false, "Processes proximity checks when feared") RULE_CATEGORY_END() RULE_CATEGORY(Mercs) @@ -269,7 +270,6 @@ RULE_INT(Zone, PEQZoneDebuff1, 4454, "First debuff casted by #peqzone Default is RULE_INT(Zone, PEQZoneDebuff2, 2209, "Second debuff casted by #peqzone Default is Tendrils of Apathy") RULE_BOOL(Zone, UsePEQZoneDebuffs, true, "Will determine if #peqzone will debuff players or not when used") RULE_REAL(Zone, HotZoneBonus, 0.75, "") -RULE_INT(Zone, ReservedInstances, 30, "Will reserve this many instance ids for globals... probably not a good idea to change this while a server is running") RULE_INT(Zone, EbonCrystalItemID, 40902, "") RULE_INT(Zone, RadiantCrystalItemID, 40903, "") RULE_BOOL(Zone, LevelBasedEXPMods, false, "Allows you to use the level_exp_mods table in consideration to your players EXP hits") @@ -774,6 +774,12 @@ RULE_BOOL(HotReload, QuestsRepopWhenPlayersNotInCombat, true, "When a hot reload RULE_BOOL(HotReload, QuestsResetTimersWithReload, true, "When a hot reload is triggered, quest timers will be reset") RULE_CATEGORY_END() +RULE_CATEGORY(Instances) +RULE_INT(Instances, ReservedInstances, 30, "Will reserve this many instance ids for globals... probably not a good idea to change this while a server is running") +RULE_BOOL(Instances, RecycleInstanceIds, true, "Will recycle free instance ids instead of gradually running out at 32k") +RULE_INT(Instances, GuildHallExpirationDays, 90, "Amount of days before a Guild Hall instance expires") +RULE_CATEGORY_END() + #undef RULE_CATEGORY #undef RULE_INT #undef RULE_REAL diff --git a/common/timer.cpp b/common/timer.cpp index 120e3dea7..f4d931764 100644 --- a/common/timer.cpp +++ b/common/timer.cpp @@ -129,13 +129,17 @@ void Timer::SetTimer(uint32 set_timer_time) { } } -uint32 Timer::GetRemainingTime() const { +uint32 Timer::GetRemainingTime() const +{ if (enabled) { - if (current_time - start_time > timer_time) + if (current_time - start_time > timer_time) { return 0; - else + } + else { return (start_time + timer_time) - current_time; - } else { + } + } + else { return 0xFFFFFFFF; } } 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/ucs/chatchannel.cpp b/ucs/chatchannel.cpp index 001f7b1e7..efac898d5 100644 --- a/ucs/chatchannel.cpp +++ b/ucs/chatchannel.cpp @@ -47,8 +47,13 @@ ChatChannel::ChatChannel(std::string inName, std::string inOwner, std::string in Moderated = false; - LogInfo("New ChatChannel created: Name: [[{}]], Owner: [[{}]], Password: [[{}]], MinStatus: [{}]", - Name.c_str(), Owner.c_str(), Password.c_str(), MinimumStatus); + LogDebug( + "New ChatChannel created: Name: [[{}]], Owner: [[{}]], Password: [[{}]], MinStatus: [{}]", + Name.c_str(), + Owner.c_str(), + Password.c_str(), + MinimumStatus + ); } @@ -154,7 +159,7 @@ void ChatChannelList::SendAllChannels(Client *c) { void ChatChannelList::RemoveChannel(ChatChannel *Channel) { - LogInfo("RemoveChannel([{}])", Channel->GetName().c_str()); + LogDebug("RemoveChannel ([{}])", Channel->GetName().c_str()); LinkedListIterator iterator(ChatChannels); @@ -175,7 +180,7 @@ void ChatChannelList::RemoveChannel(ChatChannel *Channel) { void ChatChannelList::RemoveAllChannels() { - LogInfo("RemoveAllChannels"); + LogDebug("RemoveAllChannels"); LinkedListIterator iterator(ChatChannels); @@ -242,7 +247,7 @@ void ChatChannel::AddClient(Client *c) { int AccountStatus = c->GetAccountStatus(); - LogInfo("Adding [{}] to channel [{}]", c->GetName().c_str(), Name.c_str()); + LogDebug("Adding [{}] to channel [{}]", c->GetName().c_str(), Name.c_str()); LinkedListIterator iterator(ClientsInChannel); @@ -267,7 +272,7 @@ bool ChatChannel::RemoveClient(Client *c) { if(!c) return false; - LogInfo("RemoveClient [{}] from channel [{}]", c->GetName().c_str(), GetName().c_str()); + LogDebug("RemoveClient [{}] from channel [{}]", c->GetName().c_str(), GetName().c_str()); bool HideMe = c->GetHideMe(); @@ -304,7 +309,7 @@ bool ChatChannel::RemoveClient(Client *c) { if((Password.length() == 0) || (RuleI(Channels, DeleteTimer) == 0)) return false; - LogInfo("Starting delete timer for empty password protected channel [{}]", Name.c_str()); + LogDebug("Starting delete timer for empty password protected channel [{}]", Name.c_str()); DeleteTimer.Start(RuleI(Channels, DeleteTimer) * 60000); } @@ -402,7 +407,7 @@ void ChatChannel::SendMessageToChannel(std::string Message, Client* Sender) { if(ChannelClient) { - LogInfo("Sending message to [{}] from [{}]", + LogDebug("Sending message to [{}] from [{}]", ChannelClient->GetName().c_str(), Sender->GetName().c_str()); if (cv_messages[static_cast(ChannelClient->GetClientVersion())].length() == 0) { @@ -505,7 +510,7 @@ ChatChannel *ChatChannelList::AddClientToChannel(std::string ChannelName, Client return nullptr; } - LogInfo("AddClient to channel [[{}]] with password [[{}]]", NormalisedName.c_str(), Password.c_str()); + LogDebug("AddClient to channel [[{}]] with password [[{}]]", NormalisedName.c_str(), Password.c_str()); ChatChannel *RequiredChannel = FindChannel(NormalisedName); @@ -581,7 +586,7 @@ void ChatChannelList::Process() { if(CurrentChannel && CurrentChannel->ReadyToDelete()) { - LogInfo("Empty temporary password protected channel [{}] being destroyed", + LogDebug("Empty temporary password protected channel [{}] being destroyed", CurrentChannel->GetName().c_str()); RemoveChannel(CurrentChannel); @@ -597,7 +602,7 @@ void ChatChannel::AddInvitee(const std::string &Invitee) if (!IsInvitee(Invitee)) { Invitees.push_back(Invitee); - LogInfo("Added [{}] as invitee to channel [{}]", Invitee.c_str(), Name.c_str()); + LogDebug("Added [{}] as invitee to channel [{}]", Invitee.c_str(), Name.c_str()); } } @@ -608,7 +613,7 @@ void ChatChannel::RemoveInvitee(std::string Invitee) if(it != std::end(Invitees)) { Invitees.erase(it); - LogInfo("Removed [{}] as invitee to channel [{}]", Invitee.c_str(), Name.c_str()); + LogDebug("Removed [{}] as invitee to channel [{}]", Invitee.c_str(), Name.c_str()); } } diff --git a/ucs/clientlist.cpp b/ucs/clientlist.cpp index 429f28fbf..0b1d43262 100644 --- a/ucs/clientlist.cpp +++ b/ucs/clientlist.cpp @@ -235,7 +235,7 @@ std::vector ParseRecipients(std::string RecipientString) { static void ProcessMailTo(Client *c, std::string MailMessage) { - LogInfo("MAILTO: From [{}], [{}]", c->MailBoxName().c_str(), MailMessage.c_str()); + LogDebug("MAILTO: From [{}], [{}]", c->MailBoxName().c_str(), MailMessage.c_str()); std::vector Recipients; @@ -304,7 +304,7 @@ static void ProcessMailTo(Client *c, std::string MailMessage) { if (!database.SendMail(Recipient, c->MailBoxName(), Subject, Body, RecipientsString)) { - LogInfo("Failed in SendMail([{}], [{}], [{}], [{}])", Recipient.c_str(), + LogError("Failed in SendMail([{}], [{}], [{}], [{}])", Recipient.c_str(), c->MailBoxName().c_str(), Subject.c_str(), RecipientsString.c_str()); int PacketLength = 10 + Recipient.length() + Subject.length(); @@ -556,6 +556,17 @@ void Client::CloseConnection() { ClientStream->ReleaseFromUse(); } +void Clientlist::CheckForStaleConnectionsAll() +{ + LogDebug("Checking for stale connections"); + + auto it = ClientChatConnections.begin(); + while (it != ClientChatConnections.end()) { + (*it)->SendKeepAlive(); + ++it; + } +} + void Clientlist::CheckForStaleConnections(Client *c) { if (!c) return; @@ -634,10 +645,12 @@ void Clientlist::Process() // std::string::size_type LastPeriod = MailBoxString.find_last_of("."); - if (LastPeriod == std::string::npos) + if (LastPeriod == std::string::npos) { CharacterName = MailBoxString; - else + } + else { CharacterName = MailBoxString.substr(LastPeriod + 1); + } LogInfo("Received login for user [{}] with key [{}]", MailBox, Key); @@ -652,8 +665,9 @@ void Clientlist::Process() database.GetAccountStatus((*it)); - if ((*it)->GetConnectionType() == ConnectionTypeCombined) + if ((*it)->GetConnectionType() == ConnectionTypeCombined) { (*it)->SendFriends(); + } (*it)->SendMailBoxes(); @@ -865,7 +879,9 @@ void Clientlist::CloseAllConnections() { void Client::AddCharacter(int CharID, const char *CharacterName, int Level) { if (!CharacterName) return; - LogInfo("Adding character [{}] with ID [{}] for [{}]", CharacterName, CharID, GetName().c_str()); + + LogDebug("Adding character [{}] with ID [{}] for [{}]", CharacterName, CharID, GetName().c_str()); + CharacterEntry NewCharacter; NewCharacter.CharID = CharID; NewCharacter.Name = CharacterName; @@ -874,6 +890,10 @@ void Client::AddCharacter(int CharID, const char *CharacterName, int Level) { Characters.push_back(NewCharacter); } +void Client::SendKeepAlive() { + QueuePacket(new EQApplicationPacket(OP_SessionReady, 0)); +} + void Client::SendMailBoxes() { int Count = Characters.size(); @@ -930,7 +950,7 @@ void Client::AddToChannelList(ChatChannel *JoinedChannel) { for (int i = 0; i < MAX_JOINED_CHANNELS; i++) if (JoinedChannels[i] == nullptr) { JoinedChannels[i] = JoinedChannel; - LogInfo("Added Channel [{}] to slot [{}] for [{}]", JoinedChannel->GetName().c_str(), i + 1, GetName().c_str()); + LogDebug("Added Channel [{}] to slot [{}] for [{}]", JoinedChannel->GetName().c_str(), i + 1, GetName().c_str()); return; } } @@ -2346,18 +2366,17 @@ void Client::SendFriends() { } } -std::string Client::MailBoxName() { +std::string Client::MailBoxName() +{ + if ((Characters.empty()) || (CurrentMailBox > (Characters.size() - 1))) { + LogDebug("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]", + CurrentMailBox, Characters.size()); - if ((Characters.empty()) || (CurrentMailBox > (Characters.size() - 1))) - { - LogInfo("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]", - CurrentMailBox, Characters.size()); - - return ""; + return std::string(); } - LogInfo("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]", - CurrentMailBox, Characters.size()); + LogDebug("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]", + CurrentMailBox, Characters.size()); return Characters[CurrentMailBox].Name; diff --git a/ucs/clientlist.h b/ucs/clientlist.h index 6021c5c0e..548fb170c 100644 --- a/ucs/clientlist.h +++ b/ucs/clientlist.h @@ -143,7 +143,7 @@ public: void SetConnectionType(char c); ConnectionType GetConnectionType() { return TypeOfConnection; } EQEmu::versions::ClientVersion GetClientVersion() { return ClientVersion_; } - + inline bool IsMailConnection() { return (TypeOfConnection == ConnectionTypeMail) || (TypeOfConnection == ConnectionTypeCombined); } void SendNotification(int MailBoxNumber, std::string From, std::string Subject, int MessageID); void ChangeMailBox(int NewMailBox); @@ -151,6 +151,7 @@ public: void SendFriends(); int GetCharID(); void SendUptime(); + void SendKeepAlive(); private: unsigned int CurrentMailBox; @@ -183,6 +184,7 @@ public: void Process(); void CloseAllConnections(); Client *FindCharacter(std::string CharacterName); + void CheckForStaleConnectionsAll(); void CheckForStaleConnections(Client *c); Client *IsCharacterOnline(std::string CharacterName); void ProcessOPMailCommand(Client *c, std::string CommandString); diff --git a/ucs/database.cpp b/ucs/database.cpp index d756f1ff4..108b17871 100644 --- a/ucs/database.cpp +++ b/ucs/database.cpp @@ -108,7 +108,7 @@ void Database::GetAccountStatus(Client *client) { std::string query = StringFormat( - "SELECT `status`, `hideme`, `karma`, `revoked` FROM `account` WHERE `id` = '%i' LIMIT 1", + "SELECT `status`, `hideme`, `karma`, `revoked` FROM `account` WHERE `id` = %i LIMIT 1", client->GetAccountID() ); @@ -173,7 +173,7 @@ int Database::FindAccount(const char *characterName, Client *client) query = StringFormat( "SELECT `id`, `name`, `level` FROM `character_data` " - "WHERE `account_id` = %i AND `name` != '%s'", + "WHERE `account_id` = %i AND `name` != '%s' AND deleted_at is NULL", accountID, characterName ); @@ -320,7 +320,7 @@ void Database::SendHeaders(Client *client) int unknownField3 = 1; int characterID = FindCharacter(client->MailBoxName().c_str()); - LogInfo("Sendheaders for [{}], CharID is [{}]", client->MailBoxName().c_str(), characterID); + LogDebug("Sendheaders for [{}], CharID is [{}]", client->MailBoxName().c_str(), characterID); if (characterID <= 0) { return; diff --git a/ucs/ucs.cpp b/ucs/ucs.cpp index 0f396252b..46bda530c 100644 --- a/ucs/ucs.cpp +++ b/ucs/ucs.cpp @@ -70,17 +70,18 @@ int main() { // Check every minute for unused channels we can delete // Timer ChannelListProcessTimer(60000); + Timer ClientConnectionPruneTimer(60000); Timer InterserverTimer(INTERSERVER_TIMER); // does auto-reconnect LogInfo("Starting EQEmu Universal Chat Server"); - if (!ucsconfig::LoadConfig()) { - LogInfo("Loading server configuration failed"); + if (!ucsconfig::LoadConfig()) { + LogInfo("Loading server configuration failed"); return 1; } - Config = ucsconfig::get(); + Config = ucsconfig::get(); WorldShortName = Config->ShortName; @@ -144,19 +145,26 @@ int main() { worldserver = new WorldServer; - while(RunLoops) { + auto loop_fn = [&](EQ::Timer* t) { Timer::SetCurrentTime(); g_Clientlist->Process(); - if(ChannelListProcessTimer.Check()) + if (ChannelListProcessTimer.Check()) { ChannelList->Process(); + } - EQ::EventLoop::Get().Process(); + if (ClientConnectionPruneTimer.Check()) { + g_Clientlist->CheckForStaleConnectionsAll(); + } - Sleep(5); - } + }; + + EQ::Timer process_timer(loop_fn); + process_timer.Start(32, true); + + EQ::EventLoop::Get().Run(); ChannelList->RemoveAllChannels(); diff --git a/ucs/worldserver.cpp b/ucs/worldserver.cpp index 72865e416..107624a0d 100644 --- a/ucs/worldserver.cpp +++ b/ucs/worldserver.cpp @@ -61,7 +61,7 @@ void WorldServer::ProcessMessage(uint16 opcode, EQ::Net::Packet &p) ServerPacket tpack(opcode, p); ServerPacket *pack = &tpack; - LogInfo("Received Opcode: {:#04x}", opcode); + LogNetcode("Received Opcode: {:#04x}", opcode); switch (opcode) { 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 9be222856..28b22028e 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -405,6 +405,7 @@ 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_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_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..7eaffdc6a --- /dev/null +++ b/utils/sql/peq-dump/peq-dump.sh @@ -0,0 +1,71 @@ +#!/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" +echo 'REPLACE INTO `instance_list` VALUES (1,25,1,1,0,0,1),(2,25,2,1,0,0,1),(3,151,1,1,0,0,1),(4,114,1,1,0,0,1),(5,344,1,1,0,0,1),(6,202,0,1,0,0,1);' >> "${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]" 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/client.cpp b/world/client.cpp index 2d4b65e71..18f0aa776 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -714,7 +714,7 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) { EQApplicationPacket *outapp; uint32 tmpaccid = 0; charid = database.GetCharacterInfo(char_name, &tmpaccid, &zone_id, &instance_id); - if (charid == 0 || tmpaccid != GetAccountID()) { + if (charid == 0) { LogInfo("Could not get CharInfo for [{}]", char_name); eqs->Close(); return true; 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/ucs.cpp b/world/ucs.cpp index f5910da80..cde94b8d9 100644 --- a/world/ucs.cpp +++ b/world/ucs.cpp @@ -6,29 +6,38 @@ #include "../common/misc_functions.h" #include "../common/md5.h" #include "../common/packet_dump.h" +#include "../common/event/timer.h" UCSConnection::UCSConnection() { - Stream = 0; + connection = 0; } void UCSConnection::SetConnection(std::shared_ptr inStream) { - if (Stream && Stream->Handle()) - { + if (inStream && connection && connection->Handle()) { LogInfo("Incoming UCS Connection while we were already connected to a UCS"); - Stream->Handle()->Disconnect(); + connection->Handle()->Disconnect(); + } + + connection = inStream; + if (connection) { + connection->OnMessage( + std::bind( + &UCSConnection::ProcessPacket, + this, + std::placeholders::_1, + std::placeholders::_2 + ) + ); } - Stream = inStream; - if (Stream) { - Stream->OnMessage(std::bind(&UCSConnection::ProcessPacket, this, std::placeholders::_1, std::placeholders::_2)); - } + m_keepalive.reset(new EQ::Timer(5000, true, std::bind(&UCSConnection::OnKeepAlive, this, std::placeholders::_1))); } void UCSConnection::ProcessPacket(uint16 opcode, EQ::Net::Packet &p) { - if (!Stream) + if (!connection) return; ServerPacket tpack(opcode, p); @@ -60,10 +69,10 @@ void UCSConnection::ProcessPacket(uint16 opcode, EQ::Net::Packet &p) void UCSConnection::SendPacket(ServerPacket* pack) { - if (!Stream) + if (!connection) return; - Stream->SendPacket(pack); + connection->SendPacket(pack); } void UCSConnection::SendMessage(const char *From, const char *Message) @@ -78,3 +87,13 @@ void UCSConnection::SendMessage(const char *From, const char *Message) SendPacket(pack); safe_delete(pack); } + +void UCSConnection::OnKeepAlive(EQ::Timer *t) +{ + if (!connection) { + return; + } + + ServerPacket pack(ServerOP_KeepAlive, 0); + connection->SendPacket(&pack); +} diff --git a/world/ucs.h b/world/ucs.h index d2051c0be..c32872ccb 100644 --- a/world/ucs.h +++ b/world/ucs.h @@ -4,6 +4,7 @@ #include "../common/types.h" #include "../common/net/servertalk_server_connection.h" #include "../common/servertalk.h" +#include "../common/event/timer.h" #include class UCSConnection @@ -13,11 +14,17 @@ public: void SetConnection(std::shared_ptr connection); void ProcessPacket(uint16 opcode, EQ::Net::Packet &p); void SendPacket(ServerPacket* pack); - void Disconnect() { if(Stream && Stream->Handle()) Stream->Handle()->Disconnect(); } + void Disconnect() { if(connection && connection->Handle()) connection->Handle()->Disconnect(); } void SendMessage(const char *From, const char *Message); private: - inline std::string GetIP() const { return (Stream && Stream->Handle()) ? Stream->Handle()->RemoteIP() : 0; } - std::shared_ptr Stream; + inline std::string GetIP() const { return (connection && connection->Handle()) ? connection->Handle()->RemoteIP() : 0; } + std::shared_ptr connection; + + /** + * Keepalive + */ + std::unique_ptr m_keepalive; + void OnKeepAlive(EQ::Timer *t); }; #endif /*UCS_H_*/ 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 87b98d599..b568d11af 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -32,6 +32,7 @@ #endif #include "map.h" +#include "water_map.h" extern Zone* zone; //#define LOSDEBUG 6 @@ -237,6 +238,11 @@ bool Mob::CheckWillAggro(Mob *mob) { if (!mob->CastToClient()->ClientFinishedLoading() || mob->CastToClient()->IsHoveringForRespawn() || mob->CastToClient()->bZoning) return false; } + + // We don't want to aggro clients outside of water if we're water only. + if (mob->IsClient() && mob->CastToClient()->GetLastRegion() != RegionTypeWater && IsUnderwaterOnly()) { + return false; + } /** * Pets shouldn't scan for aggro diff --git a/zone/client.cpp b/zone/client.cpp index 8c4b42957..e730b32ee 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -256,6 +256,7 @@ Client::Client(EQStreamInterface* ieqs) TotalSecondsPlayed = 0; keyring.clear(); bind_sight_target = nullptr; + p_raid_instance = nullptr; mercid = 0; mercSlot = 0; InitializeMercInfo(); @@ -1918,7 +1919,7 @@ void Client::CheckManaEndUpdate() { else if (group) { group->SendEndurancePacketFrom(this); } - + auto endurance_packet = new EQApplicationPacket(OP_EnduranceUpdate, sizeof(EnduranceUpdate_Struct)); EnduranceUpdate_Struct* endurance_update = (EnduranceUpdate_Struct*)endurance_packet->pBuffer; endurance_update->cur_end = GetEndurance(); @@ -8757,6 +8758,11 @@ void Client::CheckRegionTypeChanges() if (last_region_type == new_region) return; + // If we got out of water clear any water aggro for water only npcs + if (last_region_type == RegionTypeWater) { + entity_list.ClearWaterAggro(this); + } + // region type changed last_region_type = new_region; @@ -9198,7 +9204,7 @@ void Client::SetSecondaryWeaponOrnamentation(uint32 model_id) secondary_item->SetOrnamentationIDFile(model_id); SendItemPacket(EQEmu::invslot::slotSecondary, secondary_item, ItemPacketTrade); WearChange(EQEmu::textures::weaponSecondary, static_cast(model_id), 0); - + Message(Chat::Yellow, "Your secondary weapon appearance has been modified"); } } @@ -9287,3 +9293,41 @@ void Client::SetBotOption(BotOwnerOption boo, bool flag) { } #endif + +void Client::SendToGuildHall() +{ + std::string zone_short_name = "guildhall"; + uint32 zone_id = database.GetZoneID(zone_short_name.c_str()); + if (zone_id == 0) { + return; + } + + uint32 expiration_time = (RuleI(Instances, GuildHallExpirationDays) * 86400); + uint16 instance_id = 0; + std::string guild_hall_instance_key = fmt::format("guild-hall-instance-{}", GuildID()); + std::string instance_data = DataBucket::GetData(guild_hall_instance_key); + if (!instance_data.empty() && std::stoi(instance_data) > 0) { + instance_id = std::stoi(instance_data); + } + + if (instance_id <= 0) { + if (!database.GetUnusedInstanceID(instance_id)) { + Message(Chat::Red, "Server was unable to find a free instance id."); + return; + } + + if (!database.CreateInstance(instance_id, zone_id, 1, expiration_time)) { + Message(Chat::Red, "Server was unable to create a new instance."); + return; + } + + DataBucket::SetData( + guild_hall_instance_key, + std::to_string(instance_id), + std::to_string(expiration_time) + ); + } + + AssignToInstance(instance_id); + MovePC(345, instance_id, -1.00, -1.00, 3.34, 0, 1); +} diff --git a/zone/client.h b/zone/client.h index 58b0d9af8..be842ff50 100644 --- a/zone/client.h +++ b/zone/client.h @@ -633,6 +633,7 @@ public: void MovePC(uint32 zoneID, float x, float y, float z, float heading, uint8 ignorerestrictions = 0, ZoneMode zm = ZoneSolicited); void MovePC(float x, float y, float z, float heading, uint8 ignorerestrictions = 0, ZoneMode zm = ZoneSolicited); void MovePC(uint32 zoneID, uint32 instanceID, float x, float y, float z, float heading, uint8 ignorerestrictions = 0, ZoneMode zm = ZoneSolicited); + void SendToGuildHall(); void AssignToInstance(uint16 instance_id); void RemoveFromInstance(uint16 instance_id); void WhoAll(); @@ -691,7 +692,7 @@ public: int GetClientMaxLevel() const { return client_max_level; } void SetClientMaxLevel(int max_level) { client_max_level = max_level; } - + void CheckManaEndUpdate(); void SendManaUpdate(); void SendEnduranceUpdate(); @@ -1294,6 +1295,8 @@ public: void CheckRegionTypeChanges(); + WaterRegionType GetLastRegion() { return last_region_type; } + int32 CalcATK(); uint32 trapid; //ID of trap player has triggered. This is cleared when the player leaves the trap's radius, or it despawns. @@ -1301,6 +1304,8 @@ public: void SetLastPositionBeforeBulkUpdate(glm::vec4 in_last_position_before_bulk_update); glm::vec4 &GetLastPositionBeforeBulkUpdate(); + Raid *p_raid_instance; + protected: friend class Mob; void CalcItemBonuses(StatBonuses* newbon); @@ -1340,6 +1345,7 @@ protected: char *adv_data; private: + eqFilterMode ClientFilters[_FilterCount]; int32 HandlePacket(const EQApplicationPacket *app); void OPTGB(const EQApplicationPacket *app); @@ -1633,9 +1639,9 @@ private: bool InterrogateInventory_error(int16 head, int16 index, const EQEmu::ItemInstance* inst, const EQEmu::ItemInstance* parent, int depth); int client_max_level; - + #ifdef BOTS - + public: enum BotOwnerOption : size_t { booDeathMarquee, @@ -1652,7 +1658,7 @@ public: bool GetBotOption(BotOwnerOption boo) const; void SetBotOption(BotOwnerOption boo, bool flag = true); - + bool GetBotPulling() { return m_bot_pulling; } void SetBotPulling(bool flag = true) { m_bot_pulling = flag; } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 7173dd342..1267386eb 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -599,7 +599,7 @@ void Client::CompleteConnect() if (group) group->SendHPManaEndPacketsTo(this); } - + //bulk raid send in here eventually @@ -818,35 +818,45 @@ void Client::CompleteConnect() database.QueryDatabase( StringFormat( "UPDATE `character_data` SET `last_login` = UNIX_TIMESTAMP() WHERE id = %u", - this->CharacterID() + CharacterID() ) ); } - if (zone) { - if (zone->GetInstanceTimer()) { - uint32 ttime = zone->GetInstanceTimer()->GetRemainingTime(); - uint32 day = (ttime / 86400000); - uint32 hour = (ttime / 3600000) % 24; - uint32 minute = (ttime / 60000) % 60; - uint32 second = (ttime / 1000) % 60; - if (day) { - Message(Chat::Yellow, "%s(%u) will expire in %u days, %u hours, %u minutes, and %u seconds.", - zone->GetLongName(), zone->GetInstanceID(), day, hour, minute, second); - } - else if (hour) { - Message(Chat::Yellow, "%s(%u) will expire in %u hours, %u minutes, and %u seconds.", - zone->GetLongName(), zone->GetInstanceID(), hour, minute, second); - } - else if (minute) { - Message(Chat::Yellow, "%s(%u) will expire in %u minutes, and %u seconds.", - zone->GetLongName(), zone->GetInstanceID(), minute, second); - } - else { - Message(Chat::Yellow, "%s(%u) will expire in in %u seconds.", - zone->GetLongName(), zone->GetInstanceID(), second); - } + if (zone && zone->GetInstanceTimer()) { + + bool is_permanent = false; + uint32 remaining_time_seconds = database.GetTimeRemainingInstance(zone->GetInstanceID(), is_permanent); + uint32 day = (remaining_time_seconds / 86400); + uint32 hour = (remaining_time_seconds / 3600) % 24; + uint32 minute = (remaining_time_seconds / 60) % 60; + uint32 second = (remaining_time_seconds / 1) % 60; + + if (day) { + Message( + Chat::Yellow, "%s (%u) will expire in %u days, %u hours, %u minutes, and %u seconds.", + zone->GetLongName(), zone->GetInstanceID(), day, hour, minute, second + ); } + else if (hour) { + Message( + Chat::Yellow, "%s (%u) will expire in %u hours, %u minutes, and %u seconds.", + zone->GetLongName(), zone->GetInstanceID(), hour, minute, second + ); + } + else if (minute) { + Message( + Chat::Yellow, "%s (%u) will expire in %u minutes, and %u seconds.", + zone->GetLongName(), zone->GetInstanceID(), minute, second + ); + } + else { + Message( + Chat::Yellow, "%s (%u) will expire in in %u seconds.", + zone->GetLongName(), zone->GetInstanceID(), second + ); + } + } SendRewards(); @@ -1237,7 +1247,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) database.ClearOldRecastTimestamps(cid); /* Clear out our old recast timestamps to keep the DB clean */ // set to full support in case they're a gm with items in disabled expansion slots..but, have their gm flag off... // item loss will occur when they use the 'empty' slots, if this is not done - m_inv.SetGMInventory(true); + m_inv.SetGMInventory(true); loaditems = database.GetInventory(cid, &m_inv); /* Load Character Inventory */ database.LoadCharacterBandolier(cid, &m_pp); /* Load Character Bandolier */ database.LoadCharacterBindPoint(cid, &m_pp); /* Load Character Bind */ @@ -1341,7 +1351,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) client_max_level = GetCharMaxLevelFromBucket(); } SetClientMaxLevel(client_max_level); - + // we know our class now, so we might have to fix our consume timer! if (class_ == MONK) consume_food_timer.SetTimer(CONSUMPTION_MNK_TIMER); @@ -2840,7 +2850,7 @@ void Client::Handle_OP_ApplyPoison(const EQApplicationPacket *app) // rogue simply won't apply at all, no skill check done. uint16 poison_skill = GetSkill(EQEmu::skills::SkillApplyPoison); - + if (ChanceRoll < (.75 + poison_skill / 1000)) { ApplyPoisonSuccessResult = 1; AddProcToWeapon(poison->Proc.Effect, false, (GetDEX() / 100) + 103, POISON_PROC); @@ -3917,7 +3927,7 @@ void Client::Handle_OP_Bug(const EQApplicationPacket *app) Message(0, "Bug reporting is disabled on this server."); return; } - + if (app->size != sizeof(BugReport_Struct)) { printf("Wrong size of BugReport_Struct got %d expected %zu!\n", app->size, sizeof(BugReport_Struct)); } @@ -4018,6 +4028,14 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) return; } + // Hack for broken RoF2 which allows casting after a zoned IVU/IVA + if (invisible_undead || invisible_animals) { + BuffFadeByEffect(SE_InvisVsAnimals); + BuffFadeByEffect(SE_InvisVsUndead); + BuffFadeByEffect(SE_InvisVsUndead2); + BuffFadeByEffect(SE_Invisibility); // Included per JJ for completeness - client handles this one atm + } + CastSpell_Struct* castspell = (CastSpell_Struct*)app->pBuffer; m_TargetRing = glm::vec3(castspell->x_pos, castspell->y_pos, castspell->z_pos); @@ -4362,9 +4380,9 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { sizeof(PlayerPositionUpdateClient_Struct), app->size); return; } - + PlayerPositionUpdateClient_Struct *ppu = (PlayerPositionUpdateClient_Struct *) app->pBuffer; - + /* Boat handling */ if (ppu->spawn_id != GetID()) { /* If player is controlling boat */ @@ -4374,16 +4392,16 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { controlling_boat_id = 0; return; } - + auto boat_delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, EQ10toFloat(ppu->delta_heading)); boat->SetDelta(boat_delta); - + auto outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); PlayerPositionUpdateServer_Struct *ppus = (PlayerPositionUpdateServer_Struct *) outapp->pBuffer; boat->MakeSpawnUpdate(ppus); entity_list.QueueCloseClients(boat, outapp, true, 300, this, false); safe_delete(outapp); - + /* Update the boat's position on the server, without sending an update */ boat->GMMove(ppu->x_pos, ppu->y_pos, ppu->z_pos, EQ12toFloat(ppu->heading), false); return; @@ -4398,9 +4416,9 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { if (cmob != nullptr) { cmob->SetPosition(ppu->x_pos, ppu->y_pos, ppu->z_pos); cmob->SetHeading(EQ12toFloat(ppu->heading)); - mMovementManager->SendCommandToClients(cmob, 0.0, 0.0, 0.0, + mMovementManager->SendCommandToClients(cmob, 0.0, 0.0, 0.0, 0.0, 0, ClientRangeAny, nullptr, this); - cmob->CastToNPC()->SaveGuardSpot(glm::vec4(ppu->x_pos, + cmob->CastToNPC()->SaveGuardSpot(glm::vec4(ppu->x_pos, ppu->y_pos, ppu->z_pos, EQ12toFloat(ppu->heading))); } } @@ -4418,7 +4436,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { // From this point forward, we need to use a new set of variables for client // position. If the client is in a boat, we need to add the boat pos and // the client offset together. - + float cx = ppu->x_pos; float cy = ppu->y_pos; float cz = ppu->z_pos; @@ -4443,45 +4461,45 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { /* Check to see if PPU should trigger an update to the rewind position. */ float rewind_x_diff = 0; float rewind_y_diff = 0; - + rewind_x_diff = cx - m_RewindLocation.x; rewind_x_diff *= rewind_x_diff; rewind_y_diff = cy - m_RewindLocation.y; rewind_y_diff *= rewind_y_diff; - - /* + + /* We only need to store updated values if the player has moved. If the player has moved more than units for x or y, then we'll store his pre-PPU x and y for /rewind, in case he gets stuck. */ - + if ((rewind_x_diff > 750) || (rewind_y_diff > 750)) m_RewindLocation = glm::vec3(m_Position); - + /* If the PPU was a large jump, such as a cross zone gate or Call of Hero, just update rewind coordinates to the new ppu coordinates. This will prevent exploitation. */ - + if ((rewind_x_diff > 5000) || (rewind_y_diff > 5000)) m_RewindLocation = glm::vec3(cx, cy, cz); - + if (proximity_timer.Check()) { entity_list.ProcessMove(this, glm::vec3(cx, cy, cz)); if (RuleB(TaskSystem, EnableTaskSystem) && RuleB(TaskSystem, EnableTaskProximity)) ProcessTaskProximities(cx, cy, cz); - + m_Proximity = glm::vec3(cx, cy, cz); } - + /* Update internal state */ m_Delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, EQ10toFloat(ppu->delta_heading)); - + if (IsTracking() && ((m_Position.x != cx) || (m_Position.y != cy))) { if (zone->random.Real(0, 100) < 70)//should be good CheckIncreaseSkill(EQEmu::skills::SkillTracking, nullptr, -20); } - + /* Break Hide if moving without sneaking and set rewind timer if moved */ if (cy != m_Position.y || cx != m_Position.x) { if ((hidden || improved_hidden) && !sneaking) { @@ -4500,7 +4518,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { } rewind_timer.Start(30000, true); } - + /* Handle client aggro scanning timers NPCs */ is_client_moving = (cy == m_Position.y && cx == m_Position.x) ? false : true; @@ -4564,55 +4582,55 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { } int32 new_animation = ppu->animation; - + /* Update internal server position from what the client has sent */ m_Position.x = cx; m_Position.y = cy; m_Position.z = cz; - + /* Visual Debugging */ if (RuleB(Character, OPClientUpdateVisualDebug)) { LogDebug("ClientUpdate: ppu x: [{}] y: [{}] z: [{}] h: [{}]", cx, cy, cz, new_heading); this->SendAppearanceEffect(78, 0, 0, 0, 0); this->SendAppearanceEffect(41, 0, 0, 0, 0); } - + /* Only feed real time updates when client is moving */ if (is_client_moving || new_heading != m_Position.w || new_animation != animation) { - + animation = ppu->animation; m_Position.w = new_heading; - + /* Broadcast update to other clients */ auto outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); PlayerPositionUpdateServer_Struct *position_update = (PlayerPositionUpdateServer_Struct *) outapp->pBuffer; - + MakeSpawnUpdate(position_update); - + if (gm_hide_me) { entity_list.QueueClientsStatus(this, outapp, true, Admin(), 255); } else { entity_list.QueueCloseClients(this, outapp, true, RuleI(Range, ClientPositionUpdates), nullptr, true); } - - + + /* Always send position updates to group - send when beyond normal ClientPositionUpdate range */ Group *group = this->GetGroup(); Raid *raid = this->GetRaid(); - + if (raid) { raid->QueueClients(this, outapp, true, true, (RuleI(Range, ClientPositionUpdates) * -1)); } else if (group) { group->QueueClients(this, outapp, true, true, (RuleI(Range, ClientPositionUpdates) * -1)); } - + safe_delete(outapp); } - + if (zone->watermap) { if (zone->watermap->InLiquid(glm::vec3(m_Position))) { CheckIncreaseSkill(EQEmu::skills::SkillSwimming, nullptr, -17); - + // Dismount horses when entering water if (GetHorseId() && RuleB(Character, DismountWater)) { SetHorseId(0); @@ -5749,23 +5767,23 @@ void Client::Handle_OP_FindPersonRequest(const EQApplicationPacket *app) printf("Error in FindPersonRequest_Struct. Expected size of: %zu, but got: %i\n", sizeof(FindPersonRequest_Struct), app->size); else { FindPersonRequest_Struct* t = (FindPersonRequest_Struct*)app->pBuffer; - + std::vector points; Mob* target = entity_list.GetMob(t->npc_id); - + if (target == nullptr) { //empty length packet == not found. EQApplicationPacket outapp(OP_FindPersonReply, 0); QueuePacket(&outapp); return; } - + if (!RuleB(Pathing, Find) && RuleB(Bazaar, EnableWarpToTrader) && target->IsClient() && (target->CastToClient()->Trader || target->CastToClient()->Buyer)) { Message(Chat::Yellow, "Moving you to Trader %s", target->GetName()); MovePC(zone->GetZoneID(), zone->GetInstanceID(), target->GetX(), target->GetY(), target->GetZ(), 0.0f); } - + if (!RuleB(Pathing, Find) || !zone->pathing) { //fill in the path array... @@ -5788,40 +5806,40 @@ void Client::Handle_OP_FindPersonRequest(const EQApplicationPacket *app) { glm::vec3 Start(GetX(), GetY(), GetZ() + (GetSize() < 6.0 ? 6 : GetSize()) * HEAD_POSITION); glm::vec3 End(target->GetX(), target->GetY(), target->GetZ() + (target->GetSize() < 6.0 ? 6 : target->GetSize()) * HEAD_POSITION); - + bool partial = false; bool stuck = false; auto pathlist = zone->pathing->FindRoute(Start, End, partial, stuck); - + if (pathlist.empty() || partial) { EQApplicationPacket outapp(OP_FindPersonReply, 0); QueuePacket(&outapp); return; } - + // Live appears to send the points in this order: // Final destination. // Current Position. // rest of the points. FindPerson_Point p; - + int PointNumber = 0; - + bool LeadsToTeleporter = false; - + auto v = pathlist.back(); - + p.x = v.pos.x; p.y = v.pos.y; p.z = v.pos.z; points.push_back(p); - + p.x = GetX(); p.y = GetY(); p.z = GetZ(); points.push_back(p); - + for (auto Iterator = pathlist.begin(); Iterator != pathlist.end(); ++Iterator) { if ((*Iterator).teleport) // Teleporter @@ -5829,7 +5847,7 @@ void Client::Handle_OP_FindPersonRequest(const EQApplicationPacket *app) LeadsToTeleporter = true; break; } - + glm::vec3 v = (*Iterator).pos; p.x = v.x; p.y = v.y; @@ -5837,17 +5855,17 @@ void Client::Handle_OP_FindPersonRequest(const EQApplicationPacket *app) points.push_back(p); ++PointNumber; } - + if (!LeadsToTeleporter) { p.x = target->GetX(); p.y = target->GetY(); p.z = target->GetZ(); - + points.push_back(p); } } - + SendPathPacket(points); } } @@ -11090,14 +11108,14 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) { case RaidCommandInviteIntoExisting: case RaidCommandInvite: { - + Client *player_to_invite = entity_list.GetClientByName(raid_command_packet->player_name); if (!player_to_invite) break; Group *player_to_invite_group = player_to_invite->GetGroup(); - + if (player_to_invite->HasRaid()) { Message(Chat::Red, "%s is already in a raid.", player_to_invite->GetName()); break; @@ -11112,7 +11130,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) Message(Chat::Red, "You can only invite an ungrouped player or group leader to join your raid."); break; } - + /* Send out invite to the client */ auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidGeneral_Struct)); RaidGeneral_Struct *raid_command = (RaidGeneral_Struct*)outapp->pBuffer; @@ -11124,7 +11142,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) raid_command->action = 20; player_to_invite->QueuePacket(outapp); - + safe_delete(outapp); break; @@ -11220,7 +11238,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) } if (player_invited_group->IsLeader(player_invited_group->members[x])) { Client *c = nullptr; - + if (player_invited_group->members[x]->IsClient()) c = player_invited_group->members[x]->CastToClient(); else @@ -11230,24 +11248,24 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) raid->SendMakeLeaderPacketTo(raid->leadername, c); raid->AddMember(c, raid_free_group_id, true, true, true); raid->SendBulkRaid(c); - + if (raid->IsLocked()) { raid->SendRaidLockTo(c); } } else { Client *c = nullptr; - + if (player_invited_group->members[x]->IsClient()) c = player_invited_group->members[x]->CastToClient(); else continue; - + raid->SendRaidCreate(c); raid->SendMakeLeaderPacketTo(raid->leadername, c); raid->AddMember(c, raid_free_group_id); raid->SendBulkRaid(c); - + if (raid->IsLocked()) { raid->SendRaidLockTo(c); } @@ -11281,12 +11299,12 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) c = group->members[x]->CastToClient(); else continue; - + raid->SendRaidCreate(c); raid->SendMakeLeaderPacketTo(raid->leadername, c); raid->AddMember(c, raid_free_group_id, false, true); raid->SendBulkRaid(c); - + if (raid->IsLocked()) { raid->SendRaidLockTo(c); } @@ -11294,17 +11312,17 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) else { Client *c = nullptr; - + if (group->members[x]->IsClient()) c = group->members[x]->CastToClient(); else continue; - + raid->SendRaidCreate(c); raid->SendMakeLeaderPacketTo(raid->leadername, c); raid->AddMember(c, raid_free_group_id); raid->SendBulkRaid(c); - + if (raid->IsLocked()) { raid->SendRaidLockTo(c); } @@ -11321,7 +11339,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) if (player_invited_group) { raid = new Raid(player_accepting_invite); - + entity_list.AddRaid(raid); raid->SetRaidDetails(); Client *addClientig = nullptr; @@ -11345,7 +11363,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) raid->SendMakeLeaderPacketTo(raid->leadername, c); raid->AddMember(c, 0, true, true, true); raid->SendBulkRaid(c); - + if (raid->IsLocked()) { raid->SendRaidLockTo(c); } @@ -11470,7 +11488,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) raid->SetGroupLeader(raid_command_packet->leader_name, false); /* We were the leader of our old group */ - if (old_group < 12) { + if (old_group < 12) { /* Assign new group leader if we can */ for (int x = 0; x < MAX_RAID_MEMBERS; x++) { if (raid->members[x].GroupNumber == old_group) { @@ -11499,7 +11517,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) strn0cpy(raid_command_packet->playername, raid->members[x].membername, 64); worldserver.SendPacket(pack); - + safe_delete(pack); } break; @@ -11545,7 +11563,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) raid->SetGroupLeader(raid_command_packet->leader_name, false); for (int x = 0; x < MAX_RAID_MEMBERS; x++) { if (raid->members[x].GroupNumber == oldgrp && strlen(raid->members[x].membername) > 0 && strcmp(raid->members[x].membername, raid_command_packet->leader_name) != 0){ - + raid->SetGroupLeader(raid->members[x].membername); raid->UpdateGroupAAs(oldgrp); @@ -11568,7 +11586,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) strn0cpy(raid_command->playername, raid->members[x].membername, 64); raid_command->zoneid = zone->GetZoneID(); raid_command->instance_id = zone->GetInstanceID(); - + worldserver.SendPacket(pack); safe_delete(pack); } @@ -11583,14 +11601,14 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) else { auto pack = new ServerPacket(ServerOP_RaidGroupDisband, sizeof(ServerRaidGeneralAction_Struct)); ServerRaidGeneralAction_Struct* raid_command = (ServerRaidGeneralAction_Struct*)pack->pBuffer; - + raid_command->rid = raid->GetID(); raid_command->zoneid = zone->GetZoneID(); raid_command->instance_id = zone->GetInstanceID(); strn0cpy(raid_command->playername, raid_command_packet->leader_name, 64); worldserver.SendPacket(pack); - + safe_delete(pack); } @@ -13327,13 +13345,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 +13367,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/embparser_api.cpp b/zone/embparser_api.cpp index 871cc0ced..c65cd204c 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -822,6 +822,54 @@ XS(XS__isdisctome) { XSRETURN(1); } +XS(XS__getracename); +XS(XS__getracename) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getracename(uint16 race_id)"); + + dXSTARG; + uint16 race_id = (int) SvIV(ST(0)); + std::string race_name = quest_manager.getracename(race_id); + + sv_setpv(TARG, race_name.c_str()); + XSprePUSH; + PUSHTARG; + 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__getskillname); +XS(XS__getskillname) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getskillname(int skill_id)"); + + dXSTARG; + int skill_id = (int) SvIV(ST(0)); + std::string skill_name = quest_manager.getskillname(skill_id); + + sv_setpv(TARG, skill_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + XS(XS__safemove); XS(XS__safemove) { dXSARGS; @@ -2342,7 +2390,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 +2660,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 +2861,51 @@ 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__getnpcnamebyid); +XS(XS__getnpcnamebyid) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getnpcnamebyid(uint32 npc_id)"); + + dXSTARG; + uint32 npc_id = (int) SvIV(ST(0)); + const char *npc_name = quest_manager.getnpcnamebyid(npc_id); + + sv_setpv(TARG, npc_name); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + XS(XS__UpdateSpawnTimer); XS(XS__UpdateSpawnTimer) { dXSARGS; @@ -3063,6 +3172,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; @@ -3151,6 +3279,93 @@ XS(XS__saylink) { XSRETURN(1); } +XS(XS__getcharnamebyid); +XS(XS__getcharnamebyid) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getcharnamebyid(uint32 char_id)"); + dXSTARG; + + Const_char *RETVAL; + uint32 char_id = (int) SvUV(ST(0)); + + RETVAL = quest_manager.getcharnamebyid(char_id); + + sv_setpv(TARG, RETVAL); + XSprePUSH; + PUSHTARG; +} + +XS(XS__getcharidbyname); +XS(XS__getcharidbyname) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getcharidbyname(string name)"); + dXSTARG; + + uint32 RETVAL; + const char *name = (const char *) SvPV_nolen(ST(0)); + + RETVAL = quest_manager.getcharidbyname(name); + XSprePUSH; + PUSHu((UV)RETVAL); + + XSRETURN(1); +} + +XS(XS__getclassname); +XS(XS__getclassname) { + dXSARGS; + if (items < 1 || items > 2) + Perl_croak(aTHX_ "Usage: quest::getclassname(uint8 class_id, [uint8 level = 0])"); + dXSTARG; + + std::string RETVAL; + uint8 class_id = (int) SvUV(ST(0)); + uint8 level = 0; + if (items > 1) + level = (int) SvUV(ST(1)); + + RETVAL = quest_manager.getclassname(class_id, level); + sv_setpv(TARG, RETVAL.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + +XS(XS__getcurrencyitemid); +XS(XS__getcurrencyitemid) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getcurrencyitemid(int currency_id)"); + dXSTARG; + + int RETVAL; + int currency_id = (int) SvUV(ST(0)); + + RETVAL = quest_manager.getcurrencyitemid(currency_id); + + XSprePUSH; + PUSHi((IV)RETVAL); + XSRETURN(1); +} + +XS(XS__getcurrencyid); +XS(XS__getcurrencyid) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getcurrencyid(uint32 item_id)"); + dXSTARG; + + int RETVAL; + uint32 item_id = (int) SvUV(ST(0)); + + RETVAL = quest_manager.getcurrencyid(item_id); + XSprePUSH; + PUSHi((IV)RETVAL); + XSRETURN(1); +} + XS(XS__getguildnamebyid); XS(XS__getguildnamebyid) { dXSARGS; @@ -3169,6 +3384,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 +4142,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 +4167,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); @@ -3938,15 +4207,29 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "follow"), XS__follow, file); newXS(strcpy(buf, "forcedoorclose"), XS__forcedoorclose, file); newXS(strcpy(buf, "forcedooropen"), XS__forcedooropen, file); + newXS(strcpy(buf, "getcharidbyname"), XS__getcharidbyname, file); + newXS(strcpy(buf, "getclassname"), XS__getclassname, file); + newXS(strcpy(buf, "getcurrencyid"), XS__getcurrencyid, 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, "getnpcnamebyid"), XS__getnpcnamebyid, file); newXS(strcpy(buf, "get_spawn_condition"), XS__get_spawn_condition, file); + newXS(strcpy(buf, "getcharnamebyid"), XS__getcharnamebyid, file); + newXS(strcpy(buf, "getcurrencyitemid"), XS__getcurrencyitemid, 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, "getracename"), XS__getracename, file); + newXS(strcpy(buf, "getspellname"), XS__getspellname, file); + newXS(strcpy(buf, "getskillname"), XS__getskillname, 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/entity.cpp b/zone/entity.cpp index b13fb5649..23ff268fc 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -498,7 +498,7 @@ void EntityList::MobProcess() size_t sz = mob_list.size(); #ifdef IDLE_WHEN_EMPTY - if (numclients > 0 || + if (numclients > 0 || mob->GetWanderType() == 4 || mob->GetWanderType() == 6) { // Normal processing, or assuring that spawns that should // path and depop do that. Otherwise all of these type mobs @@ -931,12 +931,12 @@ bool EntityList::MakeDoorSpawnPacket(EQApplicationPacket *app, Client *client) memcpy(new_door.name, door->GetDoorName(), 32); auto position = door->GetPosition(); - + new_door.xPos = position.x; new_door.yPos = position.y; new_door.zPos = position.z; new_door.heading = position.w; - + new_door.incline = door->GetIncline(); new_door.size = door->GetSize(); new_door.doorId = door->GetDoorID(); @@ -1984,17 +1984,26 @@ Raid *EntityList::GetRaidByID(uint32 id) Raid *EntityList::GetRaidByClient(Client* client) { - std::list::iterator iterator; + if (client->p_raid_instance) { + return client->p_raid_instance; + } + std::list::iterator iterator; iterator = raid_list.begin(); while (iterator != raid_list.end()) { - for (int x = 0; x < MAX_RAID_MEMBERS; x++) - if ((*iterator)->members[x].member) - if((*iterator)->members[x].member == client) + for (auto &member : (*iterator)->members) { + if (member.member) { + if (member.member == client) { + client->p_raid_instance = *iterator; return *iterator; + } + } + } + ++iterator; } + return nullptr; } @@ -3277,13 +3286,15 @@ void EntityList::Evade(Mob *who) void EntityList::ClearAggro(Mob* targ) { Client *c = nullptr; - if (targ->IsClient()) + if (targ->IsClient()) { c = targ->CastToClient(); + } auto it = npc_list.begin(); while (it != npc_list.end()) { if (it->second->CheckAggro(targ)) { - if (c) + if (c) { c->RemoveXTarget(it->second, false); + } it->second->RemoveFromHateList(targ); } if (c && it->second->IsOnFeignMemory(c)) { @@ -3294,6 +3305,32 @@ void EntityList::ClearAggro(Mob* targ) } } +//removes "targ" from all hate lists of mobs that are water only. +void EntityList::ClearWaterAggro(Mob* targ) +{ + Client *c = nullptr; + if (targ->IsClient()) { + c = targ->CastToClient(); + } + auto it = npc_list.begin(); + while (it != npc_list.end()) { + if (it->second->IsUnderwaterOnly()) { + if (it->second->CheckAggro(targ)) { + if (c) { + c->RemoveXTarget(it->second, false); + } + it->second->RemoveFromHateList(targ); + } + if (c && it->second->IsOnFeignMemory(c)) { + it->second->RemoveFromFeignMemory(c); //just in case we feigned + c->RemoveXTarget(it->second, false); + } + } + ++it; + } +} + + void EntityList::ClearFeignAggro(Mob *targ) { auto it = npc_list.begin(); diff --git a/zone/entity.h b/zone/entity.h index 505f34963..3c182be29 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -447,6 +447,7 @@ public: void Process(); void ClearAggro(Mob* targ); + void ClearWaterAggro(Mob* targ); void ClearFeignAggro(Mob* targ); void ClearZoneFeignAggro(Client* targ); void AggroZone(Mob* who, uint32 hate = 0); diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 3ee86df47..f9438b32c 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -2913,23 +2913,33 @@ void Client::SendItemPacket(int16 slot_id, const EQEmu::ItemInstance* inst, Item if (!inst) return; - if (slot_id <= EQEmu::invslot::POSSESSIONS_END && slot_id >= EQEmu::invslot::POSSESSIONS_BEGIN) { - if ((((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0) - return; - } - else if (slot_id <= EQEmu::invbag::GENERAL_BAGS_END && slot_id >= EQEmu::invbag::GENERAL_BAGS_BEGIN) { - auto temp_slot = EQEmu::invslot::GENERAL_BEGIN + ((slot_id - EQEmu::invbag::GENERAL_BAGS_BEGIN) / EQEmu::invbag::SLOT_COUNT); - if ((((uint64)1 << temp_slot) & GetInv().GetLookup()->PossessionsBitmask) == 0) - return; - } - else if (slot_id <= EQEmu::invslot::BANK_END && slot_id >= EQEmu::invslot::BANK_BEGIN) { - if ((slot_id - EQEmu::invslot::BANK_BEGIN) >= GetInv().GetLookup()->InventoryTypeSize.Bank) - return; - } - else if (slot_id <= EQEmu::invbag::BANK_BAGS_END && slot_id >= EQEmu::invbag::BANK_BAGS_BEGIN) { - auto temp_slot = (slot_id - EQEmu::invbag::BANK_BAGS_BEGIN) / EQEmu::invbag::SLOT_COUNT; - if (temp_slot >= GetInv().GetLookup()->InventoryTypeSize.Bank) - return; + if (packet_type != ItemPacketMerchant) { + if (slot_id <= EQEmu::invslot::POSSESSIONS_END && slot_id >= EQEmu::invslot::POSSESSIONS_BEGIN) { + if ((((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0) { + LogError("Item not sent to merchant : slot [{}]", slot_id); + return; + } + } + else if (slot_id <= EQEmu::invbag::GENERAL_BAGS_END && slot_id >= EQEmu::invbag::GENERAL_BAGS_BEGIN) { + auto temp_slot = EQEmu::invslot::GENERAL_BEGIN + ((slot_id - EQEmu::invbag::GENERAL_BAGS_BEGIN) / EQEmu::invbag::SLOT_COUNT); + if ((((uint64)1 << temp_slot) & GetInv().GetLookup()->PossessionsBitmask) == 0) { + LogError("Item not sent to merchant2 : slot [{}]", slot_id); + return; + } + } + else if (slot_id <= EQEmu::invslot::BANK_END && slot_id >= EQEmu::invslot::BANK_BEGIN) { + if ((slot_id - EQEmu::invslot::BANK_BEGIN) >= GetInv().GetLookup()->InventoryTypeSize.Bank) { + LogError("Item not sent to merchant3 : slot [{}]", slot_id); + return; + } + } + else if (slot_id <= EQEmu::invbag::BANK_BAGS_END && slot_id >= EQEmu::invbag::BANK_BAGS_BEGIN) { + auto temp_slot = (slot_id - EQEmu::invbag::BANK_BAGS_BEGIN) / EQEmu::invbag::SLOT_COUNT; + if (temp_slot >= GetInv().GetLookup()->InventoryTypeSize.Bank) { + LogError("Item not sent to merchant4 : slot [{}]", slot_id); + return; + } + } } // Serialize item into |-delimited string (Titanium- uses '|' delimiter .. newer clients use pure data serialization) diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 9d0873360..955130922 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -90,6 +90,11 @@ void Lua_Client::SetPVP(bool v) { self->SetPVP(v); } +void Lua_Client::SendToGuildHall() { + Lua_Safe_Call_Void(); + self->SendToGuildHall(); +} + bool Lua_Client::GetPVP() { Lua_Safe_Call_Bool(); return self->GetPVP(); @@ -1584,6 +1589,7 @@ luabind::scope lua_register_client() { .def("Disconnect", (void(Lua_Client::*)(void))&Lua_Client::Disconnect) .def("IsLD", (bool(Lua_Client::*)(void))&Lua_Client::IsLD) .def("WorldKick", (void(Lua_Client::*)(void))&Lua_Client::WorldKick) + .def("SendToGuildHall", (void(Lua_Client::*)(void))&Lua_Client::SendToGuildHall) .def("GetAnon", (bool(Lua_Client::*)(void))&Lua_Client::GetAnon) .def("Duck", (void(Lua_Client::*)(void))&Lua_Client::Duck) .def("Stand", (void(Lua_Client::*)(void))&Lua_Client::Stand) diff --git a/zone/lua_client.h b/zone/lua_client.h index aef0378cb..b0eb719e8 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -39,6 +39,7 @@ public: void Disconnect(); bool IsLD(); void WorldKick(); + void SendToGuildHall(); bool GetAnon(); void Duck(); void Stand(); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 0d1086ad1..d047238de 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -393,6 +393,18 @@ bool lua_is_disc_tome(int item_id) { return quest_manager.isdisctome(item_id); } +std::string lua_get_race_name(uint32 race_id) { + return quest_manager.getracename(race_id); +} + +std::string lua_get_spell_name(uint32 spell_id) { + return quest_manager.getspellname(spell_id); +} + +std::string lua_get_skill_name(int skill_id) { + return quest_manager.getskillname(skill_id); +} + void lua_safe_move() { quest_manager.safemove(); } @@ -729,6 +741,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 +797,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 +823,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); @@ -854,10 +878,50 @@ bool lua_delete_data(std::string bucket_key) { return DataBucket::DeleteData(bucket_key); } +const char *lua_get_char_name_by_id(uint32 char_id) { + return database.GetCharNameByID(char_id); +} + +uint32 lua_get_char_id_by_name(const char* name) { + return quest_manager.getcharidbyname(name); +} + +std::string lua_get_class_name(uint8 class_id) { + return quest_manager.getclassname(class_id); +} + +std::string lua_get_class_name(uint8 class_id, uint8 level) { + return quest_manager.getclassname(class_id, level); +} + +int lua_get_currency_id(uint32 item_id) { + return quest_manager.getcurrencyid(item_id); +} + +int lua_get_currency_item_id(int currency_id) { + return quest_manager.getcurrencyitemid(currency_id); +} + 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); +} + +const char *lua_get_npc_name_by_id(uint32 npc_id) { + return quest_manager.getnpcnamebyid(npc_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 +974,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 +1700,9 @@ 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_race_name", (std::string(*)(uint16))&lua_get_race_name), + luabind::def("get_spell_name", (std::string(*)(uint32))&lua_get_spell_name), + luabind::def("get_skill_name", (std::string(*)(int))&lua_get_skill_name), luabind::def("safe_move", &lua_safe_move), luabind::def("rain", &lua_rain), luabind::def("snow", &lua_snow), @@ -1699,6 +1770,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 +1784,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), @@ -1727,7 +1801,17 @@ luabind::scope lua_register_general() { luabind::def("set_data", (void(*)(std::string, std::string))&lua_set_data), 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_char_name_by_id", &lua_get_char_name_by_id), + luabind::def("get_char_id_by_name", (uint32(*)(const char*))&lua_get_char_id_by_name), + luabind::def("get_class_name", (std::string(*)(uint8))&lua_get_class_name), + luabind::def("get_class_name", (std::string(*)(uint8,uint8))&lua_get_class_name), + luabind::def("get_currency_id", &lua_get_currency_id), + luabind::def("get_currency_item_id", &lua_get_currency_item_id), 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_npc_name_by_id", &lua_get_npc_name_by_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 +1826,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_ai.cpp b/zone/mob_ai.cpp index 43d85c450..b687c0c8a 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -747,6 +747,13 @@ void Client::AI_Process() RunTo(m_FearWalkTarget.x, m_FearWalkTarget.y, m_FearWalkTarget.z); } } + if (RuleB(Character, ProcessFearedProximity) && proximity_timer.Check()) { + entity_list.ProcessMove(this, glm::vec3(GetX(), GetY(), GetZ())); + if (RuleB(TaskSystem, EnableTaskSystem) && RuleB(TaskSystem, EnableTaskProximity)) + ProcessTaskProximities(GetX(), GetY(), GetZ()); + + m_Proximity = glm::vec3(GetX(), GetY(), GetZ()); + } return; } diff --git a/zone/npc.cpp b/zone/npc.cpp index 6df237d5d..39b1b046b 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -694,11 +694,6 @@ void NPC::RemoveCash() { bool NPC::Process() { - if (IsStunned() && stunned_timer.Check()) { - Mob::UnStun(); - this->spun_timer.Disable(); - } - if (p_depop) { Mob* owner = entity_list.GetMob(this->ownerid); @@ -711,6 +706,11 @@ bool NPC::Process() } return false; } + + if (IsStunned() && stunned_timer.Check()) { + Mob::UnStun(); + this->spun_timer.Disable(); + } SpellProcess(); @@ -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_client.cpp b/zone/perl_client.cpp index 2390dea3a..e46b5e8d4 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -245,6 +245,27 @@ XS(XS_Client_WorldKick) { XSRETURN_EMPTY; } +XS(XS_Client_SendToGuildHall); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_SendToGuildHall) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::SendToGuildHall(THIS)"); + { + Client *THIS; + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV *) SvRV(ST(0))); + THIS = INT2PTR(Client *, tmp); + } else + Perl_croak(aTHX_ "THIS is not of type Client"); + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SendToGuildHall(); + } + XSRETURN_EMPTY; +} + XS(XS_Client_GetAnon); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_GetAnon) { dXSARGS; @@ -2439,7 +2460,7 @@ XS(XS_Client_MemmedCount) { RETVAL = THIS->MemmedCount(); XSprePUSH; - PUSHu((UV) RETVAL); + PUSHu((UV) RETVAL); } XSRETURN(1); } @@ -4786,7 +4807,7 @@ XS(XS_Client_AddLevelBasedExp) { if (items > 2) max_level = (uint8) SvUV(ST(2)); - + if (items > 3) ignore_mods = (bool) SvTRUE(ST(3)); @@ -6564,6 +6585,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "SendSound"), XS_Client_SendSound, file, "$"); newXSproto(strcpy(buf, "SendSpellAnim"), XS_Client_SendSpellAnim, file, "$$$"); newXSproto(strcpy(buf, "SendTargetCommand"), XS_Client_SendTargetCommand, file, "$$"); + newXSproto(strcpy(buf, "SendToGuildHall"), XS_Client_SendToGuildHall, file, "$"); newXSproto(strcpy(buf, "SendWebLink"), XS_Client_SendWebLink, file, "$:$"); newXSproto(strcpy(buf, "SendZoneFlagInfo"), XS_Client_SendZoneFlagInfo, file, "$$"); newXSproto(strcpy(buf, "SetAAPoints"), XS_Client_SetAAPoints, file, "$$"); 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..ba6a5340c 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -906,6 +906,31 @@ bool QuestManager::isdisctome(int item_id) { return(true); } +std::string QuestManager::getracename(uint16 race_id) { + return GetRaceIDName(race_id); +} + +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; +} + +std::string QuestManager::getskillname(int skill_id) { + if (skill_id >= 0 && skill_id < EQEmu::skills::SkillCount) { + std::map Skills = EQEmu::skills::GetSkillTypeMap(); + for (auto skills_iter : Skills) { + if (skill_id == skills_iter.first) { + return skills_iter.second; + } + } + } + return std::string(); +} + void QuestManager::safemove() { QuestManagerCurrentQuestVars(); if (initiator && initiator->IsClient()) @@ -2432,6 +2457,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 +2615,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 +2731,23 @@ 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; +} + +const char *QuestManager::getnpcnamebyid(uint32 npc_id) { + if (npc_id > 0) { + return database.GetNPCNameByID(npc_id); + } + return ""; +} + uint16 QuestManager::CreateInstance(const char *zone, int16 version, uint32 duration) { QuestManagerCurrentQuestVars(); @@ -2811,6 +2889,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(); @@ -2869,6 +2951,45 @@ std::string QuestManager::saylink(char *saylink_text, bool silent, const char *l return EQEmu::SayLinkEngine::GenerateQuestSaylink(saylink_text, silent, link_name); } +const char* QuestManager::getcharnamebyid(uint32 char_id) { + if (char_id > 0) { + return database.GetCharNameByID(char_id); + } + return ""; +} + +uint32 QuestManager::getcharidbyname(const char* name) { + return database.GetCharacterID(name); +} + +std::string QuestManager::getclassname(uint8 class_id, uint8 level) { + return GetClassIDName(class_id, level); +} + +int QuestManager::getcurrencyid(uint32 item_id) { + auto iter = zone->AlternateCurrencies.begin(); + while (iter != zone->AlternateCurrencies.end()) { + if (item_id == (*iter).item_id) { + return (*iter).id; + } + ++iter; + } + return 0; +} + +int QuestManager::getcurrencyitemid(int currency_id) { + if (currency_id > 0) { + auto iter = zone->AlternateCurrencies.begin(); + while (iter != zone->AlternateCurrencies.end()) { + if (currency_id == (*iter).id) { + return (*iter).item_id; + } + ++iter; + } + } + return 0; +} + const char* QuestManager::getguildnamebyid(int guild_id) { if (guild_id > 0) return guild_mgr.GetGuildName(guild_id); @@ -2876,6 +2997,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..14ddea9c8 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -107,6 +107,9 @@ public: void level(int newlevel); void traindisc(int discipline_tome_item_id); bool isdisctome(int item_id); + std::string getracename(uint16 race_id); + std::string getspellname(uint32 spell_id); + std::string getskillname(int skill_id); void safemove(); void rain(int weather); void snow(int weather); @@ -213,12 +216,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 +248,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); @@ -250,7 +257,16 @@ public: void FlagInstanceByRaidLeader(uint32 zone, int16 version); const char* varlink(char* perltext, int item_id); std::string saylink(char *saylink_text, bool silent, const char *link_name); + const char* getcharnamebyid(uint32 char_id); + uint32 getcharidbyname(const char* name); + std::string getclassname(uint8 class_id, uint8 level = 0); + int getcurrencyid(uint32 item_id); + int getcurrencyitemid(int currency_id); const char* getguildnamebyid(int guild_id); + int getguildidbycharid(uint32 char_id); + int getgroupidbycharid(uint32 char_id); + const char* getnpcnamebyid(uint32 npc_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..428b17843 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -177,6 +177,7 @@ void Raid::RemoveMember(const char *characterName) if(client) { client->SetRaidGrouped(false); client->LeaveRaidXTargets(this); + client->p_raid_instance = nullptr; } auto pack = new ServerPacket(ServerOP_RaidRemove, sizeof(ServerRaidGeneralAction_Struct)); @@ -728,15 +729,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 +815,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()); } } } @@ -1073,8 +1079,9 @@ void Raid::SendRaidRemoveAll(const char *who) void Raid::SendRaidDisband(Client *to) { - if(!to) + if (!to) { return; + } auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidGeneral_Struct)); RaidGeneral_Struct *rg = (RaidGeneral_Struct*)outapp->pBuffer; @@ -1609,7 +1616,7 @@ void Raid::SendHPManaEndPacketsFrom(Mob *mob) return; uint32 group_id = 0; - + if(mob->IsClient()) group_id = this->GetGroup(mob->CastToClient()); @@ -1617,7 +1624,7 @@ void Raid::SendHPManaEndPacketsFrom(Mob *mob) EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); mob->CreateHPPacket(&hpapp); - + for(int x = 0; x < MAX_RAID_MEMBERS; x++) { if(members[x].member) { if(!mob->IsClient() || ((members[x].member != mob->CastToClient()) && (members[x].GroupNumber == group_id))) { @@ -1628,7 +1635,7 @@ void Raid::SendHPManaEndPacketsFrom(Mob *mob) mana_update->spawn_id = mob->GetID(); mana_update->mana = mob->GetManaPercent(); members[x].member->QueuePacket(&outapp, false); - + outapp.SetOpcode(OP_MobEnduranceUpdate); MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer; endurance_update->endurance = mob->GetEndurancePercent(); 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..d9378a23c 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,9 @@ 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)){ + + auto spellOwner = GetOwnerOrSelf(); + if(!IsEffectInSpell(spell_id, SE_BindAffinity) && !is_damage_or_lifetap_spell){ entity_list.QueueCloseClients( spelltar, /* Sender */ message_packet, /* Packet */ @@ -3990,6 +3991,13 @@ 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 && spellOwner->IsClient()) { + spellOwner->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..4b2034067 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: { @@ -1874,9 +1881,11 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } case ServerOP_UCSServerStatusReply: { - auto ucsss = (UCSServerStatus_Struct*)pack->pBuffer; - if (zone) + auto ucsss = (UCSServerStatus_Struct *) pack->pBuffer; + if (zone) { zone->SetUCSServerAvailable((ucsss->available != 0), ucsss->timestamp); + LogInfo("UCS Server is now [{}]", (ucsss->available == 1 ? "online" : "offline")); + } break; } case ServerOP_CZSetEntityVariableByNPCTypeID: diff --git a/zone/zone.cpp b/zone/zone.cpp index 16cdc0af8..d64c198d3 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -140,7 +140,7 @@ bool Zone::Bootup(uint32 iZoneID, uint32 iInstanceID, bool iStaticZone) { if(iInstanceID != 0) { auto pack = new ServerPacket(ServerOP_AdventureZoneData, sizeof(uint16)); - *((uint16*)pack->pBuffer) = iInstanceID; + *((uint16*)pack->pBuffer) = iInstanceID; worldserver.SendPacket(pack); delete pack; } @@ -330,81 +330,156 @@ bool Zone::LoadGroundSpawns() { return(true); } -int Zone::SaveTempItem(uint32 merchantid, uint32 npcid, uint32 item, int32 charges, bool sold) { - int freeslot = 0; - std::list merlist = merchanttable[merchantid]; - std::list::const_iterator itr; - uint32 i = 1; - for (itr = merlist.begin(); itr != merlist.end(); ++itr) { - MerchantList ml = *itr; - if (ml.item == item) - return 0; - - // Account for merchant lists with gaps in them. - if (ml.slot >= i) - i = ml.slot + 1; - } +void Zone::DumpMerchantList(uint32 npcid) { std::list tmp_merlist = tmpmerchanttable[npcid]; std::list::const_iterator tmp_itr; - bool update_charges = false; TempMerchantList ml; - while (freeslot == 0 && !update_charges) { - freeslot = i; - for (tmp_itr = tmp_merlist.begin(); tmp_itr != tmp_merlist.end(); ++tmp_itr) { - ml = *tmp_itr; - if (ml.item == item) { - update_charges = true; - freeslot = 0; - break; - } - if ((ml.slot == i) || (ml.origslot == i)) { - freeslot = 0; - } - } - i++; + + for (tmp_itr = tmp_merlist.begin(); tmp_itr != tmp_merlist.end(); ++tmp_itr) { + ml = *tmp_itr; + + LogInventory("slot[{}] Orig[{}] Item[{}] Charges[{}]", ml.slot, ml.origslot, ml.item, ml.charges); } - if (update_charges) { +} + +int Zone::SaveTempItem(uint32 merchantid, uint32 npcid, uint32 item, int32 charges, bool sold) { + + LogInventory("Transaction of [{}] [{}]", charges, item); + //DumpMerchantList(npcid); + // Iterate past main items. + // If the item being transacted is in this list, return 0; + std::list merlist = merchanttable[merchantid]; + std::list::const_iterator itr; + uint32 temp_slot_index = 1; + for (itr = merlist.begin(); itr != merlist.end(); ++itr) { + MerchantList ml = *itr; + if (ml.item == item) { + return 0; + } + + // Account for merchant lists with gaps in them. + if (ml.slot >= temp_slot_index) { + temp_slot_index = ml.slot + 1; + } + } + + LogInventory("Searching Temporary List. Main list ended at [{}]", temp_slot_index-1); + + // Now search the temporary list. + std::list tmp_merlist = tmpmerchanttable[npcid]; + std::list::const_iterator tmp_itr; + TempMerchantList ml; + uint32 first_empty_slot = 0; // Save 1st vacant slot while searching.. + bool found = false; + + for (tmp_itr = tmp_merlist.begin(); tmp_itr != tmp_merlist.end(); ++tmp_itr) { + ml = *tmp_itr; + + if (ml.item == item) { + found = true; + LogInventory("Item found in temp list at [{}] with [{}] charges", ml.origslot, ml.charges); + break; + } + } + + if (found) { tmp_merlist.clear(); std::list oldtmp_merlist = tmpmerchanttable[npcid]; for (tmp_itr = oldtmp_merlist.begin(); tmp_itr != oldtmp_merlist.end(); ++tmp_itr) { TempMerchantList ml2 = *tmp_itr; if(ml2.item != item) tmp_merlist.push_back(ml2); + else { + if (sold) { + LogInventory("Total charges is [{}] + [{}] charges", ml.charges, charges); + ml.charges = ml.charges + charges; + } + else { + ml.charges = charges; + LogInventory("new charges is [{}] charges", ml.charges); + } + + if (!ml.origslot) { + ml.origslot = ml.slot; + } + + if (charges > 0) { + database.SaveMerchantTemp(npcid, ml.origslot, item, ml.charges); + tmp_merlist.push_back(ml); + } + else { + database.DeleteMerchantTemp(npcid, ml.origslot); + } + } } - if (sold) - ml.charges = ml.charges + charges; - else - ml.charges = charges; - if (!ml.origslot) - ml.origslot = ml.slot; - if (charges > 0) { - database.SaveMerchantTemp(npcid, ml.origslot, item, ml.charges); - tmp_merlist.push_back(ml); - } - else { - database.DeleteMerchantTemp(npcid, ml.origslot); - } + tmpmerchanttable[npcid] = tmp_merlist; - - if (sold) - return ml.slot; - + //DumpMerchantList(npcid); + return ml.slot; } - if (freeslot) { - if (charges < 0) //sanity check only, shouldnt happen + else { + if (charges < 0) { //sanity check only, shouldnt happen charges = 0x7FFF; - database.SaveMerchantTemp(npcid, freeslot, item, charges); + } + + // Find an ununsed db slot # + std::list slots; + TempMerchantList ml3; + for (tmp_itr = tmp_merlist.begin(); tmp_itr != tmp_merlist.end(); ++tmp_itr) { + ml3 = *tmp_itr; + slots.push_back(ml3.origslot); + } + slots.sort(); + std::list::const_iterator slots_itr; + uint32 first_empty_slot = 0; + uint32 idx = temp_slot_index; + for (slots_itr = slots.begin(); slots_itr != slots.end(); ++slots_itr) { + if (!first_empty_slot && *slots_itr > idx) { + LogInventory("Popped [{}]", *slots_itr); + LogInventory("First Gap Found at [{}]", idx); + break; + } + + ++idx; + } + + first_empty_slot = idx; + + // Find an ununsed mslot + slots.clear(); + for (tmp_itr = tmp_merlist.begin(); tmp_itr != tmp_merlist.end(); ++tmp_itr) { + ml3 = *tmp_itr; + slots.push_back(ml3.slot); + } + slots.sort(); + uint32 first_empty_mslot=0; + idx = temp_slot_index; + for (slots_itr = slots.begin(); slots_itr != slots.end(); ++slots_itr) { + if (!first_empty_mslot && *slots_itr > idx) { + LogInventory("Popped [{}]", *slots_itr); + LogInventory("First Gap Found at [{}]", idx); + break; + } + + ++idx; + } + + first_empty_mslot = idx; + + database.SaveMerchantTemp(npcid, first_empty_slot, item, charges); tmp_merlist = tmpmerchanttable[npcid]; TempMerchantList ml2; ml2.charges = charges; + LogInventory("Adding slot [{}] with [{}] charges.", first_empty_mslot, charges); ml2.item = item; ml2.npcid = npcid; - ml2.slot = freeslot; - ml2.origslot = ml2.slot; + ml2.slot = first_empty_mslot; + ml2.origslot = first_empty_slot; tmp_merlist.push_back(ml2); tmpmerchanttable[npcid] = tmp_merlist; + //DumpMerchantList(npcid); + return ml2.slot; } - return freeslot; } uint32 Zone::GetTempMerchantQuantity(uint32 NPCID, uint32 Slot) { @@ -413,8 +488,10 @@ uint32 Zone::GetTempMerchantQuantity(uint32 NPCID, uint32 Slot) { std::list::const_iterator Iterator; for (Iterator = TmpMerchantList.begin(); Iterator != TmpMerchantList.end(); ++Iterator) - if ((*Iterator).slot == Slot) + if ((*Iterator).slot == Slot) { + LogInventory("Slot [{}] has [{}] charges.", Slot, (*Iterator).charges); return (*Iterator).charges; + } return 0; } @@ -491,7 +568,7 @@ void Zone::LoadNewMerchantData(uint32 merchantid) { void Zone::GetMerchantDataForZoneLoad() { LogInfo("Loading Merchant Lists"); - std::string query = StringFormat( + std::string query = StringFormat( "SELECT " "DISTINCT ml.merchantid, " "ml.slot, " @@ -734,7 +811,7 @@ void Zone::Shutdown(bool quiet) if (RuleB(Zone, KillProcessOnDynamicShutdown)) { LogInfo("[KillProcessOnDynamicShutdown] Shutting down"); - std::exit(EXIT_SUCCESS); + EQ::EventLoop::Get().Shutdown(); } } @@ -816,7 +893,7 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name) { LogDebug("Graveyard ID is [{}]", graveyard_id()); bool GraveYardLoaded = database.GetZoneGraveyard(graveyard_id(), &pgraveyard_zoneid, &m_Graveyard.x, &m_Graveyard.y, &m_Graveyard.z, &m_Graveyard.w); - + if (GraveYardLoaded) { LogDebug("Loaded a graveyard for zone [{}]: graveyard zoneid is [{}] at [{}]", short_name, graveyard_zoneid(), to_string(m_Graveyard).c_str()); } @@ -907,7 +984,7 @@ Zone::~Zone() { //Modified for timezones. bool Zone::Init(bool iStaticZone) { SetStaticZone(iStaticZone); - + //load the zone config file. if (!LoadZoneCFG(zone->GetShortName(), zone->GetInstanceVersion())) // try loading the zone name... LoadZoneCFG(zone->GetFileName(), zone->GetInstanceVersion()); // if that fails, try the file name, then load defaults @@ -1089,7 +1166,7 @@ bool Zone::LoadZoneCFG(const char* filename, uint16 instance_id) if (instance_id != 0) { safe_delete_array(map_name); - if(!database.GetZoneCFG(database.GetZoneID(filename), 0, &newzone_data, can_bind, can_combat, can_levitate, + if(!database.GetZoneCFG(database.GetZoneID(filename), 0, &newzone_data, can_bind, can_combat, can_levitate, can_castoutdoor, is_city, is_hotzone, allow_mercs, max_movement_update_range, zone_type, default_ruleset, &map_name)) { LogError("Error loading the Zone Config"); @@ -2455,3 +2532,13 @@ void Zone::SetQuestHotReloadQueued(bool in_quest_hot_reload_queued) { quest_hot_reload_queued = in_quest_hot_reload_queued; } + +uint32 Zone::GetInstanceTimeRemaining() const +{ + return instance_time_remaining; +} + +void Zone::SetInstanceTimeRemaining(uint32 instance_time_remaining) +{ + Zone::instance_time_remaining = instance_time_remaining; +} diff --git a/zone/zone.h b/zone/zone.h index 1243e18c2..6d8fc54e6 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -163,6 +163,7 @@ public: inline void ShowNPCGlobalLoot(Client *to, NPC *who) { m_global_loot.ShowNPCGlobalLoot(to, who); } inline void ShowZoneGlobalLoot(Client *to) { m_global_loot.ShowZoneGlobalLoot(to); } int GetZoneTotalBlockedSpells() { return zone_total_blocked_spells; } + void DumpMerchantList(uint32 npcid); int SaveTempItem(uint32 merchantid, uint32 npcid, uint32 item, int32 charges, bool sold = false); int32 MobsAggroCount() { return aggroedmobs; } @@ -278,6 +279,9 @@ public: ZonePoint *GetClosestZonePoint(const glm::vec3 &location, uint32 to, Client *client, float max_distance = 40000.0f); ZonePoint *GetClosestZonePointWithoutZone(float x, float y, float z, Client *client, float max_distance = 40000.0f); + uint32 GetInstanceTimeRemaining() const; + void SetInstanceTimeRemaining(uint32 instance_time_remaining); + /** * GMSay Callback for LogSys * @@ -361,6 +365,7 @@ private: uint8 zone_type; uint16 instanceversion; uint32 instanceid; + uint32 instance_time_remaining; uint32 pgraveyard_id, pgraveyard_zoneid; uint32 pMaxClients; uint32 zoneid; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index cad4e1df8..1dfbeb4a2 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -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;